整理 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 就對了!