前言
在 Java 集合排序中,Comparable 與 Comparator 是兩大核心介面。前者定義「自然順序」,後者提供「定製順序」。本文深度解析兩者區別、實現細節、泛型陷阱與實戰最佳實踐。
核心介面對比
| 維度 | Comparable<T> | Comparator<T> |
| 所在包 | java.lang | java.util |
| 核心方法 | int compareTo(T o) | int compare(T o1, T o2) |
| 設計意圖 | 類內部定義自然順序 | 類外部定義多種排序策略 |
| 修改源碼 | 需要 (實現介面) | 不需要 (獨立類/匿名類/Lambda) |
| 排序調用 | Collections.sort(list) | Collections.sort(list, comparator) |
| 適用場景 | 單一、固有排序邏輯 | 多種、可變、第三方類排序 |
Comparable 自然順序實現
1. 標準實現模板
public class Person implements Comparable<Person> {
private String name;
private int age;
private double height;
// 建構子、getter/setter 省略
@Override
public int compareTo(Person other) {
// 1. 空值防護
if (other == null) return 1; // 或拋出 NPE
// 2. 主鍵比較:按年齡升序
int ageCompare = Integer.compare(this.age, other.age);
if (ageCompare != 0) return ageCompare;
// 3. 次鍵比較:按身高降序
int heightCompare = Double.compare(other.height, this.height); // 降序
if (heightCompare != 0) return heightCompare;
// 4. 終極鍵:按姓名字典序
return this.name.compareTo(other.name);
}
}
2. 關鍵注意事項
- 一致性:
compareTo 與 equals 應保持一致 (即 compareTo==0 ⟺ equals==true),否則在 TreeSet/TreeMap 中會出現邏輯錯誤
- 傳遞性:a > b 且 b > c ⇒ a > c
- 對稱性:a > b ⇒ b < a
- 避免減法溢出:錯誤
return this.age - other.age; → 正確 Integer.compare(this.age, other.age)
Comparator 定製排序實現
1. 獨立類實現 (Java 7 風格)
public class PersonHeightComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
// 空值處理策略:null 排最後
if (p1 == null && p2 == null) return 0;
if (p1 == null) return 1;
if (p2 == null) return -1;
return Double.compare(p1.getHeight(), p2.getHeight());
}
}
// 使用
Collections.sort(personList, new PersonHeightComparator());
2. Java 8 Lambda 簡寫
// 按姓名長度升序
list.sort(Comparator.comparing(Person::getName, Comparator.comparingInt(String::length)));
// 多鍵排序:年齡升序 → 身高降序 → 姓名升序
list.sort(Comparator
.comparing(Person::getAge)
.thenComparing(Person::getHeight, Comparator.reverseOrder())
.thenComparing(Person::getName));
// 空值友好:nullsLast / nullsFirst
list.sort(Comparator
.nullsLast(Comparator.comparing(Person::getName)));
3. 比較器工廠方法總結
| 方法 | 說明 |
comparing(Function) | 提取鍵值比較 |
comparingInt/Long/Double | 基本類型優化,避免裝箱 |
thenComparing(...) | 鏈式多鍵排序 |
reversed() | 反轉順序 |
nullsFirst/Last() | 空值處理策略 |
實戰場景對比
場景一:實體類可控,單一排序需求
// 直接實現 Comparable
public class Employee implements Comparable<Employee> {
private String id;
// ...
@Override
public int compareTo(Employee o) {
return this.id.compareTo(o.id); // 自然順序:按 ID
}
}
// 使用
Collections.sort(employees); // 簡潔
場景二:實體類不可控 (第三方/舊代碼),或需多種排序
// 定義多種比較器
public class ProductComparators {
public static final Comparator<Product> BY_PRICE_ASC =
Comparator.comparingDouble(Product::getPrice);
public static final Comparator<Product> BY_SALES_DESC =
Comparator.comparingLong(Product::getSales).reversed();
public static final Comparator<Product> BY_RATING_THEN_PRICE =
Comparator.comparingDouble(Product::getRating).reversed()
.thenComparingDouble(Product::getPrice);
}
// 靈活選擇
products.sort(ProductComparators.BY_PRICE_ASC);
products.sort(ProductComparators.BY_RATING_THEN_PRICE);
場景三:TreeSet/TreeMap 鍵排序
// 必須提供 Comparator (若鍵未實現 Comparable)
TreeMap<Product, Integer> stockMap = new TreeMap<>(
Comparator.comparing(Product::getId)
);
// 或鍵實現 Comparable
TreeSet<Employee> empSet = new TreeSet<>(); // Employee 實現 Comparable
進階技巧與避坑指南
1. 序列化兼容性
// Comparator 實現類最好實現 Serializable
public class SalaryComparator implements Comparator<Employee>, Serializable {
// ...
}
2. 比較器注入策略模式
public class ReportService {
private final Comparator<Employee> comparator;
// 建構子注入,支援不同報表排序策略
public ReportService(Comparator<Employee> comparator) {
this.comparator = comparator;
}
public List<Employee> generateRanking(List<Employee> data) {
return data.stream()
.sorted(comparator)
.collect(Collectors.toList());
}
}
3. 避免常見錯誤
| 錯誤代碼 | 問題 | 修正 |
return o1.age - o2.age; |
整數溢出導致符號錯誤 |
Integer.compare(o1.age, o2.age) |
return o1.name.compareTo(o2.name); |
NPE 風險 |
Comparator.nullsLast(Comparator.naturalOrder()) |
| compareTo 與 equals 不一致 |
TreeSet 去重失效 |
確保 compareTo==0 等價於 equals |
總結
- Comparable:類內部定義「自然順序」,單一、穩定、配合
Collections.sort(list) 使用
- Comparator:類外部定義「定製順序」,多策略、可注入、配合
list.sort(comparator) 使用
- Java 8+:優先使用
Comparator.comparing... 鏈式 API,類型安全、可讀性強、效能優化
- 最佳實踐:實體類實現
Comparable 定義默認順序;業務層定義 Comparator 常量滿足多樣化排序需求