1. 經典集合中的順序問題
在 Java 中一直有能保證元素順序的集合(例如 ArrayList、LinkedList、LinkedHashSet、LinkedHashMap),也有不保證順序的集合(例如 HashSet、HashMap)。但這些集合都有一個共同的缺點:即便其中一些會以特定順序儲存元素,標準介面(List、Set、Map)並未提供通用的方法來存取第一個與最後一個元素,或將順序反轉。
例如,若你有 List<String>,可以透過 list.get(0) 取得第一個元素,但對於 Set 或 Map 就行不通——你得使用 iterator 或撰寫額外程式碼。說真的,這既不方便,也不利於可讀性。
這就好像你有一個帶抽屜的櫃子,但要拿到第一個或最後一個抽屜時,每次都得手動把所有抽屜數過一遍!若能有「第一」與「最後」的專用把手,不是更好嗎?
SequencedCollection 與相關介面的出現
在 Java 21 中出現了新的集合介面:
- SequencedCollection<E>
- SequencedSet<E>
- SequencedMap<K, V>
這些介面擴充了標準集合,並引入了統一的元素順序處理方式。現在可以針對任何重視順序的集合撰寫通用的程式碼,而不必糾結具體實作。
它們是什麼?
- SequencedCollection 是一種具有明確順序的集合,可輕鬆取得第一個、最後一個元素,並可反轉順序。
- SequencedSet 同上,但用於集合(元素唯一)。
- SequencedMap 同上,但用於對映(鍵-值)。
哪些集合現在實作了它們?
在 Java 21 中,以下標準集合已實作這些新介面:
- ArrayList、LinkedList → SequencedCollection
- LinkedHashSet、TreeSet → SequencedSet
- LinkedHashMap、TreeMap → SequencedMap
這表示只要你已經在使用這些集合,就能自動獲得這些新功能!
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:例如,HashSet 與 HashMap 不保證順序,因此不會實作這些新介面。
- 方法可能拋出例外:若集合為空,呼叫 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 版本!
GO TO FULL VERSION