【Effective Java 14】考慮實現 Comparable 介面
阿新 • • 發佈:2022-04-13
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
介面約定會破壞其他依賴於比較關係的類或方法,如 TreeSet
、TreeMap
、工具類 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
介面。