Effective Java Item14 考慮實作 Comparable
整理 Effective Java 書中 Item 14: Consider implementing Comparable 心得筆記
主旨
如果你的類別有「自然順序」(例如時間、數字、字母順序),實作 Comparable 介面將大大提升它的實用性。這麼做可以讓物件支援排序、搜尋、去重,並輕鬆整合進 TreeSet、TreeMap、Arrays.sort 等 API 中,幾乎所有 Java 的值物件(例如 String、BigDecimal、Enum)都有實作 Comparable。
基本觀念:Comparable 是什麼?
Comparable 是一個泛型介面,裡面只有一個方法:
public interface Comparable<T> {
int compareTo(T o);
}
你實作這個方法後,Java 就知道怎麼幫你的物件做排序了。排序邏輯依照回傳值定義:
- 回傳負數:代表 this 小於 o
- 回傳 0:代表兩者相等
- 回傳正數:代表 this 大於 o
例如:
public class Student implements Comparable<Student> {
int score;
@Override
public int compareTo(Student other) {
return Integer.compare(this.score, other.score);
}
}
怎麼使用 compareTo 實作排序?
你可以從最重要的欄位開始比對,如果相同,再比下一個欄位。這種寫法類似排序規則的「第一優先、第二優先、第三優先」。
public int compareTo(PhoneNumber pn) {
int result = Short.compare(areaCode, pn.areaCode);
if (result == 0) {
result = Short.compare(prefix, pn.prefix);
if (result == 0) {
result = Short.compare(lineNum, pn.lineNum);
}
}
return result;
}
如果想更簡潔,Java 8 提供了 Comparator 的建構方法,可以把這些邏輯串起來:
private static final Comparator<PhoneNumber> COMPARATOR =
Comparator.comparingInt((PhoneNumber pn) -> pn.areaCode)
.thenComparingInt(pn -> pn.prefix)
.thenComparingInt(pn -> pn.lineNum);
public int compareTo(PhoneNumber pn) {
return COMPARATOR.compare(this, pn);
}
這種寫法雖然執行稍慢(約慢 10%),但可讀性更高,也比較不容易出錯。
注意事項:compareTo 合約與 equals 的一致性
compareTo要符合 對稱性、傳遞性、一致性(跟 equals 很像)- 最好讓
compareTo(a, b) == 0時,也要讓a.equals(b) == true - 否則像
TreeSet這種集合可能只看 compareTo,不看 equals,會導致行為不一致
舉例來說:
new BigDecimal("1.0").equals(new BigDecimal("1.00")) // false
new BigDecimal("1.0").compareTo(new BigDecimal("1.00")) // 0
用 HashSet 會存兩個元素,但用 TreeSet 只會存一個。
如果真的無法保證一致性,請在類別說明中明確註記:
注意:本類別的自然排序與 equals 不一致。
真實世界範例
假設你有個訂單系統,Order 類別根據下單時間排序,只要這樣實作:
public class Order implements Comparable<Order> {
private LocalDateTime orderTime;
@Override
public int compareTo(Order o) {
return orderTime.compareTo(o.orderTime);
}
}
之後就能直接用 Collections.sort() 排序,也能用 TreeSet<Order> 來做自動排序+去重,大幅減少自訂 comparator 或排序邏輯的需求。
小結
實作 Comparable 是 Java 類別的重要升級,能讓你善用排序與泛型集合的功能,簡化邏輯與提升整合性。寫 compareTo() 時請使用 compare() 系列方法取代 < 與 >,避免錯誤與溢位問題。如果類別有明確的排序規則,不要猶豫,實作 Comparable 就對了!