1. 程式人生 > 其它 >【Effective Java 14】考慮實現 Comparable 介面

【Effective Java 14】考慮實現 Comparable 介面

1. 什麼時候應該讓類實現 Comparable 介面

Comparable 介面是一個泛型介面,程式碼如下:

public interface Comparable<T> {
    int compareTo(T t);
}

類實現 Comparable 介面,就表明它的例項具有內在的排序關係,比如按照字母順序、按數值順序或者按年代順序,那你就應該考慮實現 Comparable 介面:

一般來說,值類適合實現 Comparable 介面。

2. 使用 Comparable 介面的約定

將一個物件與另一個物件進行比較時。當物件小於、等於或大於指定物件的時候,分別返回一個負整數、零或正整數。如果由於指定物件的型別而無法與該物件進行比較,則丟擲 ClassCastException

異常。

實現 Comparable 介面的類,在實現時應該滿足以下約定(注意,在下面的說明中,符號 sgn(...) 表示數學中的 signum 函式,它根據表示式的值為負、零、正,分別返回-1,0,1):

  • 實現者必須確保所有的 x 和 y 都滿足 sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) (交換律)
  • 實現者必須確保比較關係可以傳遞:(x.compareTo(y) > 0 && y.compareTo(z) > 0)x.comareTo(z) > 0;(傳遞律)
  • 實現著必須確保 x.compareTo(y) == 0
    則對於所有的 z 都滿足 sgn(x.compareTo(z)) == sgn(y.compareTo(z))
  • 強烈建議對於值類,滿足(x.compareTo(y) == 0) == (x.equals(y))。但也並非絕對必要,如果違反這一條件,都應該在文件中明確予以說明。

注意,Comparable 介面不能跨越不同型別進行比較,在比較不同型別的物件時,可以在 compareTo 函式中丟擲 ClassCastException 異常來拒絕執行。

違反 Comparable 介面約定會破壞其他依賴於比較關係的類或方法,如 TreeSetTreeMap、工具類 Collections

Arrays,因為它們內部都用與排序相關的演算法。

3. 使用 Comparable 介面的建議

  • compareTo 方法中使用關係操作符><去比較基本型別是十分繁瑣的,或者使用->0判斷。對於基本型別在 compareTo 函 數中進行比較應該使用靜態方法代替,如Integer.compare(a, b)Float.compare(a, b)Double.compare(a, b) 等等。
// 太繁瑣
public int compare(Integer A, Integer B) {
    if A > B {
        return 1;
    } else if A < B {
        return -1;
    } else {
        return 0;
    }
}

// 有溢位風險
public int compare(Integer A, Integer B) {
	return A - B;
}

// 最佳
public int compare(Integer A, Integer B) {
	return Integer.compare(A, B);
}
  • 如果一個類有多個關鍵域,按照什麼樣的順序來比較這些域是十分關鍵的。你必須從最關鍵的域開始,逐步進行到所有重要的域。

4. 使用 Comparator 介面

  • Comparator 介面配置了一組比較構造器方法,使得比較器的構造工作變得更加流暢。之後,按照 Comparable 介面的要求,這些比較器可以用來實現一個 compareTo 方法。如下:
private static class PhoneNumber implements Comparable<PhoneNumber>{
    private final short areaCode;
    private final short prefix;
    private final short lineNum;

    public PhoneNumber(short areaCode, short prefix, short lineNum) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNum = lineNum;
    }

    // 使用 Comparator 可以輕鬆實現對關鍵域按優先順序進行比較
    private static final Comparator<PhoneNumber> COMPARATOR =
            Comparator
                    .comparingInt((ToIntFunction<PhoneNumber>) value -> value.areaCode)
                    .thenComparingInt(value -> value.prefix)
                    .thenComparingInt(value -> value.lineNum);

    @Override
    public int compareTo(PhoneNumber o) {
        return COMPARATOR.compare(this, o);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PhoneNumber that = (PhoneNumber) o;
        return areaCode == that.areaCode && prefix == that.prefix && lineNum == that.lineNum;
    }

    @Override
    public int hashCode() {
        return Objects.hash(areaCode, prefix, lineNum);
    }
}

5. 總結

  • 對於排序敏感類,都應該讓該類實現 Comparable 介面
  • compareTo 函式中避免使用 > 或 < 操作符,不要通過減法運算去進行判斷,有可能溢位
  • 如果 compareTo 函式編寫起來比較複雜,比如關鍵域較多的情況,應該單獨實現一個靜態Comparator 比較構造器,並通過它簡介實現 comparaTo 介面。