CodeGym /課程 /JAVA 25 SELF /新的集合:SequencedCollection, SequencedSet, SequencedMap

新的集合:SequencedCollection, SequencedSet, SequencedMap

JAVA 25 SELF
等級 34 , 課堂 4
開放

1. 經典集合中的順序問題

在 Java 中一直有能保證元素順序的集合(例如 ArrayListLinkedListLinkedHashSetLinkedHashMap),也有不保證順序的集合(例如 HashSetHashMap)。但這些集合都有一個共同的缺點:即便其中一些會以特定順序儲存元素,標準介面(ListSetMap)並未提供通用的方法來存取第一個與最後一個元素,或將順序反轉。

例如,若你有 List<String>,可以透過 list.get(0) 取得第一個元素,但對於 SetMap 就行不通——你得使用 iterator 或撰寫額外程式碼。說真的,這既不方便,也不利於可讀性。

這就好像你有一個帶抽屜的櫃子,但要拿到第一個或最後一個抽屜時,每次都得手動把所有抽屜數過一遍!若能有「第一」與「最後」的專用把手,不是更好嗎?

SequencedCollection 與相關介面的出現

Java 21 中出現了新的集合介面:

  • SequencedCollection<E>
  • SequencedSet<E>
  • SequencedMap<K, V>

這些介面擴充了標準集合,並引入了統一的元素順序處理方式。現在可以針對任何重視順序的集合撰寫通用的程式碼,而不必糾結具體實作。

它們是什麼?

  • SequencedCollection 是一種具有明確順序的集合,可輕鬆取得第一個、最後一個元素,並可反轉順序。
  • SequencedSet 同上,但用於集合(元素唯一)。
  • SequencedMap 同上,但用於對映(鍵-值)。

哪些集合現在實作了它們?

Java 21 中,以下標準集合已實作這些新介面:

  • ArrayListLinkedListSequencedCollection
  • LinkedHashSetTreeSetSequencedSet
  • LinkedHashMapTreeMapSequencedMap

這表示只要你已經在使用這些集合,就能自動獲得這些新功能!

2. 主要方法 SequencedCollection, SequencedSet, SequencedMap

SequencedCollection 的方法

E getFirst();      // 取得第一個元素
E getLast();       // 取得最後一個元素
SequencedCollection<E> reversed(); // 取得反向順序的集合

SequencedSet 的方法

SequencedCollection 相同,另外還有 Set 的所有功能。

SequencedMap 的方法

Map.Entry<K, V> firstEntry();    // 第一個元素(鍵-值)
Map.Entry<K, V> lastEntry();     // 最後一個元素(鍵-值)
SequencedMap<K, V> reversed();   // 反向順序的 Map

3. 新介面的使用範例

範例 1:取得第一個與最後一個元素

import java.util.*;

public class SequencedDemo {
    public static void main(String[] args) {
        SequencedCollection<String> sc = new ArrayList<>();
        sc.add("Java");
        sc.add("Python");
        sc.add("Kotlin");

        // 取得第一個與最後一個元素
        String first = sc.getFirst(); // "Java"
        String last = sc.getLast();   // "Kotlin"

        System.out.println("第一個: " + first);
        System.out.println("最後一個: " + last);
    }
}

輸出:

第一個: Java
最後一個: Kotlin

範例 2:反轉集合

SequencedCollection<String> sc = new LinkedList<>();
sc.add("A");
sc.add("B");
sc.add("C");

SequencedCollection<String> reversed = sc.reversed();
System.out.println(reversed); // [C, B, A]

請注意:reversed() 會回傳集合的反向視圖。如果修改原始集合,reversed 視圖也會跟著改變!

範例 3:使用 SequencedSet

SequencedSet<Integer> set = new LinkedHashSet<>();
set.add(100);
set.add(200);
set.add(300);

System.out.println("第一個元素: " + set.getFirst()); // 100
System.out.println("最後一個元素: " + set.getLast()); // 300

SequencedSet<Integer> reversedSet = set.reversed();
System.out.println(reversedSet); // [300, 200, 100]

範例 4:使用 SequencedMap

SequencedMap<String, Integer> map = new LinkedHashMap<>();
map.put("apple", 5);
map.put("banana", 3);
map.put("cherry", 7);

Map.Entry<String, Integer> first = map.firstEntry();
Map.Entry<String, Integer> last = map.lastEntry();

System.out.println("第一個: " + first.getKey() + " = " + first.getValue()); // apple = 5
System.out.println("最後一個: " + last.getKey() + " = " + last.getValue()); // cherry = 7

SequencedMap<String, Integer> reversedMap = map.reversed();
System.out.println(reversedMap); // {cherry=7, banana=3, apple=5}

4. 這與你的應用程式有何關聯?

假設在你的學習型應用程式中,你會儲存登入使用者的清單,並希望能快速取得第一位與最後一位使用者(例如顯示「誰最先登入」與「誰最後登入」)。以前通常會寫成:

List<String> users = new ArrayList<>();
// ... 加入使用者
String first = users.get(0);
String last = users.get(users.size() - 1);

但若集合不是 List,而是例如 LinkedHashSet(元素唯一且保留插入順序),這個技巧就不適用:

Set<String> users = new LinkedHashSet<>();
// ... 加入使用者
// 要怎麼取得第一個?只能透過 iterator:
String first = users.iterator().next();
// 那最後一個呢?只能把所有元素遍歷一遍!

現在更簡單也更通用:

SequencedSet<String> users = new LinkedHashSet<>();
// ... 加入使用者
String first = users.getFirst();
String last = users.getLast();

這不僅減少程式碼量,還讓程式更易讀且更安全。

5. 新介面示意圖

classDiagram
    Collection <|-- SequencedCollection
    List <|-- SequencedCollection
    Set <|-- SequencedSet
    Map <|-- SequencedMap
    SequencedCollection <|-- SequencedSet
    SequencedSet <|-- LinkedHashSet
    SequencedSet <|-- TreeSet
    SequencedCollection <|-- ArrayList
    SequencedCollection <|-- LinkedList
    SequencedMap <|-- LinkedHashMap
    SequencedMap <|-- TreeMap

6. 實用細節

SequencedCollection 的實務優點

  • 單一介面處理順序:不必再記得哪裡用 get(0)、哪裡需要 iterator、哪裡根本拿不到第一個元素。
  • 操作佇列與堆疊更方便:輕鬆取得第一個(head)與最後一個(tail)元素。
  • 安全性與可讀性:減少誤用集合造成的錯誤;程式碼更自我說明。
  • 快速反轉集合:reversed() 讓你不需手動處理就能取得反向順序。
  • 易於維護與擴充:未來若想把例如 ArrayList 換成 LinkedHashSet,使用 SequencedCollection 的程式碼無需重寫。

實作上的特性

  • 並非所有集合都實作 SequencedCollection:例如,HashSetHashMap 不保證順序,因此不會實作這些新介面。
  • 方法可能拋出例外:若集合為空,呼叫 getFirst()getLast() 會導致 NoSuchElementException。別忘了先確認集合不為空!
  • reversed() 是視圖,而非拷貝:對原始集合的修改會反映在反向視圖,反之亦然。
  • 相容性:這些新介面僅自 Java 21 起可用。若你使用較舊版本,目前無法使用(但這正是升級的好時機!)。
  • 泛型:所有新介面完全支援泛型,因此可搭配任何資料型別。

7. 使用 SequencedCollection 的常見錯誤

錯誤 1:期望不保證順序的集合仍能提供順序。 如果你嘗試把 HashSet 轉型成 SequencedSet,會得到編譯錯誤——HashSet 不具備順序,也不實作該介面。

錯誤 2:忽略空集合。 在空集合上呼叫 getFirst()getLast() 會拋出例外。呼叫這些方法前,請先確認集合不為空:

if (!sc.isEmpty()) {
    String first = sc.getFirst();
}

錯誤 3:對 reversed() 的誤解。 方法 reversed() 回傳的是視圖,而不是拷貝。若你修改 reversed 視圖,原始集合也會被修改(反之亦然)。如果你沒有預期這種行為,可能會導致出乎意料的結果。

錯誤 4:在舊版 Java 上使用新介面。 如果你的專案以低於 Java 21 的版本編譯,編譯器將找不到這些介面。請在專案設定中檢查你的 JDK 版本!

1
問卷/小測驗
現代集合,等級 34,課堂 4
未開放
現代集合
現代集合與不可變性
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION