Collections.sort(list) 能直接對 String、Date、包裝類型排序,是因為它們實現了 java.lang.Comparable<T> 接口,提供了自然頩序。JDK 中實現 Comparable 的核心類及其排序規則:
| 類別 | 自然頩序 |
|---|---|
| Byte, Short, Integer, Long, Float, Double, BigInteger, BigDecimal | 帶符號數值升序 |
| Character | 無符號數值升序 (Unicode 碼點) |
| String | 字典序 (逐字符比較 Unicode 值) |
| Date | 時間先後升序 |
| File | 系統相關路徑字典序 |
| CollationKey | 特定 Locale 字典序 |
注意:不同類型之間不可相互比較 (拋 ClassCastException),且列表元素必須相互可比 (mutually comparable)。
public final class Name implements Comparable<Name> {
private final String firstName, lastName; // 不變類
public Name(String firstName, String lastName) {
if (firstName == null || lastName == null)
throw new NullPointerException();
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
// equals/hashCode 契約:放入 Set/Map 鍵必須重寫
@Override public boolean equals(Object o) {
if (!(o instanceof Name)) return false;
Name n = (Name) o;
return firstName.equals(n.firstName) && lastName.equals(n.lastName);
}
@Override public int hashCode() {
return 31 * firstName.hashCode() + lastName.hashCode();
}
@Override public String toString() { return firstName + " " + lastName; }
// 核心:比較邏輯 —— 先姓後名
@Override public int compareTo(Name n) {
int lastCmp = lastName.compareTo(n.lastName);
return (lastCmp != 0) ? lastCmp : firstName.compareTo(n.firstName);
}
}
final,無 Setter,構造器驗證非空 → 適合作 Set 元素/Map 鍵。(a.compareTo(b) == 0) == a.equals(b),否則 TreeSet/TreeMap 行為異常。return a - b 整數溢出陷阱。當需要非自然頩序或類不可修改時,使用 Comparator<T>。
public class EmployeeRecord implements Comparable<EmployeeRecord> {
public Name getName() { ... }
public int getEmployeeNumber() { ... }
public Date getHireDate() { ... } // 自然頩序按 name
// ...
}
// Comparator 實現 (Java 8+ Lambda 風格)
Comparator<EmployeeRecord> SENIORITY_ORDER = (e1, e2) -> {
int dateCmp = e2.getHireDate().compareTo(e1.getHireDate()); // 倒序
if (dateCmp != 0) return dateCmp;
// 同日期按工號升序,保證 Total Order (TreeSet 安全)
return Integer.compare(e1.getEmployeeNumber(), e2.getEmployeeNumber());
};
Comparator.comparing(EmployeeRecord::getHireDate).reversed().thenComparingInt(EmployeeRecord::getEmployeeNumber)Comparator.nullsFirst/Last()。Serializable 才能在分布式集合中使用。TreeSet/TreeMap 要求 Comparator 實現全序 (Total Order):compare(a,b)==0 必須等價於 a.equals(b)。
錯誤示例:僅按 hireDate 排序 → 同一天入職員工視為重複 → 第二個無法加入 TreeSet。
修正:添加 tie-breaker (如工號) 確保唯一性,如上例所示。
List<EmployeeRecord> emps = ...;
// 1. 按部門升序,部門內按薪資降序,薪資同按姓名升序
emps.sort(Comparator
.comparing(EmployeeRecord::getDept)
.thenComparing(EmployeeRecord::getSalary, Comparator.reverseOrder())
.thenComparing(EmployeeRecord::getName));
// 2. 反轉現有 Comparator
Comparator<EmployeeRecord> byNameDesc = Comparator
.comparing(EmployeeRecord::getName)
.reversed();
| 場景 | 推薦方案 |
|---|---|
| 類本身有明顯「自然頩序」且極少變化 | 實現 Comparable<T> |
| 需要多種排序方式 / 第三方類 / 條件排序 | 提供一個或多個 Comparator<T> (靜態常量或 Lambda) |
| 用於 TreeSet/TreeMap | 務必保證 compare==0 <=> equals,必要時加 tie-breaker |
| Java 8+ 環境 | 優先用 Comparator.comparing/thenComparing 鏈式 API,可讀性最強 |
掌握
Comparable與Comparator的契約與最佳實踐,是高效使用 Java 集合框架、避免生產環境排序異常的基石。