| 特性 | Comparable (內部比較) | Comparator (外部比較) |
|---|---|---|
| 定義位置 | 類別內部 implements Comparable<T> | 獨立類別 implements Comparator<T> |
| 方法簽名 | int compareTo(T o) | int compare(T o1, T o2) |
| 排序策略數量 | 唯一「自然排序」 | 無限多種 (依場景注入) |
| 適用場景 | 領域物件有明確預設順序 (如 ID、時間) | 需多種排序、無法修改原類、第三方類 |
| Collections.sort 叫法 | sort(list) | sort(list, comparator) |
public class User implements Comparable<User> {
private Long id;
private String name;
private LocalDate birthDate;
@Override
public int compareTo(User other) {
// 1. 防呆
if (other == null) return 1; // null 排最後
// 2. 主鍵優先 (穩定、唯一)
int cmp = Long.compare(this.id, other.id);
// 3. 若主鍵相同再比次要欄位 (可選)
// return cmp != 0 ? cmp : this.birthDate.compareTo(other.birthDate);
return cmp;
}
}
鐵律:compareTo 必須與 equals 一致 (即 a.compareTo(b)==0 <=> a.equals(b)),否則 TreeSet/TreeMap 會出現「明明不 equal 卻被視為重複」的詭異行為。
public class UserByAgeDesc implements Comparator<User>, Serializable {
@Override
public int compare(User u1, User u2) {
if (u1 == u2) return 0;
if (u1 == null) return 1;
if (u2 == null) return -1;
return Integer.compare(u2.getAge(), u1.getAge()); // 降序
}
}
// 單欄位
users.sort(Comparator.comparing(User::getAge)); // 升序
users.sort(Comparator.comparing(User::getAge).reversed()); // 降序
// 多欄位鏈式 (年齡升序 → 姓名降序 → ID 升序)
users.sort(Comparator
.comparing(User::getAge)
.thenComparing(Comparator.comparing(User::getName).reversed())
.thenComparing(User::getId));
// null 安全 (null 排最後)
users.sort(Comparator.nullsLast(Comparator.comparing(User::getName)));
public final class UserComparators {
private UserComparators() {}
public static final Comparator<User> BY_AGE_ASC = Comparator.comparing(User::getAge);
public static final Comparator<User> BY_NAME_DESC = Comparator.comparing(User::getName).reversed();
public static final Comparator<User> BY_REG_DATE = Comparator.comparing(User::getRegisterTime);
}
// 單元測試範例
@Test
void compareToConsistentWithEquals() {
User a = new User(1L, "Alice", 20);
User b = new User(1L, "Alice", 20); // 同 ID
User c = new User(2L, "Bob", 25);
assertEquals(0, a.compareTo(b));
assertTrue(a.equals(b)); // 必須為 true
assertNotEquals(0, a.compareTo(c));
}
若 Comparator 實例會被放入 TreeSet/TreeMap 並需跨 JVM 傳輸 (如分散式快取),必須實作 Serializable,否則反序列化會噴 NotSerializableException。
// 避免自動裝箱
Comparator<Int> intCmp = (a, b) -> Integer.compare(a, b); // 仍會裝箱
// 真正零裝箱需用專用庫 (如 Trove) 或手寫 primitive 迴圈排序
// 錯誤:reversed() 只反轉當前比較器,不影響 thenComparing 後續
Comparator<User> bad = Comparator.comparing(User::getAge).reversed()
.thenComparing(User::getName); // 年齡降序、姓名「升序」✓
// 正確:想全部反轉用比較器包裝
Comparator<User> allDesc = Comparator
.comparing(User::getAge)
.thenComparing(User::getName)
.reversed(); // 年齡降序、姓名降序
Comparable 定義「這個物件本質上怎麼排」,Comparator 定義「這個場景下怎麼排」。善用 Java 8 Comparator.comparing/thenComparing/reversed/nullsFirst 鏈式 API,能以極少程式碼表達複雜多層排序,且型別安全、易測試、易維護。