物件排序是資料處理的基石。Java 透過 Comparable(自然順序)與 Comparator(外部比較器)雙軌制,配合 SortedSet/SortedMap 實現強大的有序集合體系。本文從介面定義、實作範例、進階技巧到有序介面完整剖析。
public interface Comparable<T> {
int compareTo(T o);
}
回傳負/零/正分別代表「小於/等於/大於」。String、Date、包裝類型等 JDK 核心類均實作此介面。
public class Name implements Comparable<Name> {
private final String firstName, lastName; // 不變性
public Name(String first, String last) {
if (first == null || last == null) throw new NullPointerException();
this.firstName = first; this.lastName = last;
}
// equals/hashCode 必須一致
public boolean equals(Object o) { ... }
public int hashCode() { return 31*firstName.hashCode() + lastName.hashCode(); }
public String toString() { return firstName + " " + lastName; }
// 比較邏輯:姓氏優先,再名字
public int compareTo(Name n) {
int lastCmp = lastName.compareTo(n.lastName);
return (lastCmp != 0) ? lastCmp : firstName.compareTo(n.firstName);
}
}
關鍵點:
equals/hashCode/compareTo **三者語義一致**compareTo 標準模式:主要鍵比較 → 次要鍵 → …ClassCastException 處理型別不符(而非回傳 false)List<Name> list = Arrays.asList(
new Name("John", "Lennon"),
new Name("Karl", "Marx"),
new Name("Groucho", "Marx"),
new Name("Oscar", "Grouch")
);
Collections.sort(list);
// [Oscar Grouch, John Lennon, Groucho Marx, Karl Marx]
public interface Comparator<T> {
int compare(T o1, T o2);
}
封裝排序邏輯,適用於:
Comparable 的類別
// EmployeeRecord 自然順序按姓名排序
// 需求:按雇用日期倒序(資歷深者優先)
// 版本 1:簡單但有缺陷
Comparator<EmployeeRecord> SENIORITY_ORDER = (r1, r2) ->
r2.hireDate().compareTo(r1.hireDate()); // 倒序
// 缺陷:雇用日相同視為「相等」,TreeSet 會視為重複元素丟棄
Comparator<EmployeeRecord> SENIORITY_ORDER = (r1, r2) -> {
int dateCmp = r2.hireDate().compareTo(r1.hireDate());
if (dateCmp != 0) return dateCmp;
// 次要鍵:員工編號唯一識別
return Integer.compare(r1.employeeNumber(), r2.employeeNumber());
};
陷阱警示:切勿用 r1.num() - r2.num() 替代 Integer.compare —— 整數溢出會違反傳遞性,導致極難排查的錯誤。
public interface SortedSet<E> extends Set<E> {
// 區間視圖
SortedSet<E> subSet(E from, E to); // [from, to)
SortedSet<E> headSet(E to); // [首, to)
SortedSet<E> tailSet(E from); // [from, 尾]
// 端點
E first(); E last();
// 比較器存取
Comparator<? super E> comparator(); // 自然順序回傳 null
}
List.subList,可長期持有使用[low, high),含低不含高
// 統計 "doorbell" 到 "pickle" 之間單詞數(含低不含高)
int count = dict.subSet("doorbell", "pickle").size();
// 刪除所有以 'f' 開頭的元素
dict.subSet("f", "g").clear();
// 閉區間 [low, high] 技巧:high 後附加 '\0'
int count = dict.subSet("doorbell", "pickle\0").size();
// 開區間 (low, high) 技巧:low 後附加 '\0'
int count = dict.subSet("doorbell\0", "pickle").size();
// 分冊視圖
SortedSet<String> vol1 = dict.headSet("n"); // a-m
SortedSet<String> vol2 = dict.tailSet("n"); // n-z
first()/last() 取首尾元素ss.headSet(o).last() 取得 o 的前驅,但效率低(每次查找)public interface SortedMap<K,V> extends Map<K,V> {
Comparator<? super K> comparator();
SortedMap<K,V> subMap(K from, K to);
SortedMap<K,V> headMap(K to);
SortedMap<K,V> tailMap(K from);
K firstKey(); K lastKey();
}
所有 SortedSet 慣用語透過「鍵/值/項視圖」直接映射至 SortedMap,僅需微調型別。
SortedSet/SortedMap 實現類必須提供:
Comparator 參數建構子:指定比較器SortedSet/Map 參數建構子:複製並保持相同排序**注意**:建構子選擇基於**編譯時期參數型別**,非執行期型別。
掌握比較器鏈、全序實作與區間視圖技巧,能讓有序集合操作既優雅又高效。