JDK原始碼閱讀-Comparable介面

七印miss發表於2019-01-13

概述

Comparable在java.lang包下,閱讀原始碼後發現其內部只有一個方法compareTo()。從詞根上分析,Comparable以-able結尾,表示它有自身具備的某種能力的性質,表明了Comparable物件本身是可以與同型別進行比較的,其比較方法為compareTo。

Comparable介面類圖
其定義如下:

public interface Comparable<T> {
    public int compareTo(T o);
}
複製程式碼

關注點

  • Comparable介面會對實現它的類施加(impose)一個整體的順序。這一順序被認為是類的自然排序,類的compareTo方法被認為是它的自然比較方法。
  • 實現此介面的物件列表(和陣列)可以通過Collections.sort(和 Arrays.sort)進行自動排序。實現這一介面的物件在有序Map中,有序是按照key進行排序的;在有序Set中,是按照set集合中的元素排序的。而使用這些方法時,我們並不需要指定比較器comparator(說明:這些排序都是預設升序排序,且排序欄位只有一個。如果一個類有多個排序欄位,要對這個類集合進行排序,則需要重寫比較器方法)。
  • 對於類C的任意變數e1和e2,當且僅當e1.compareTo(e2) == 0e1.equals(e2)具有相同的boolean值時,類C的自然排序才叫做與equals一致。注意,null不是任何類的例項,即使 e.equals(null)返回falsee.compareTo(null)也將丟擲 NullPointerException
  • 我們強烈建議(儘管並不是必須的):自然排序應該和equals結果保持一致。是因為自然排序用到了compareTo方法,這裡的意思是需要滿足關係:e1.compareTo(e2) == 0的和1.equals(e2)有相同的返回。這是因為沒有明確比較器的有序set(和有序map),如果自然排序不能和equals方法保持一致,那麼它們會表現出一些詭異的行為。而且,這樣的有序set(或者map)和equals中通用規範是矛盾的。
  • 舉個例子:如果向一個沒有明確比較器的有序set中新增2個值a和b。a.equals(b)值為false,而a.compareTo(b) == 0值為true,那麼第二次的add操作會失敗。因為從有序set的角度看,a和b是等值的。(出現這種事情就很詭異了,明明a在add之後,b再add時,這是兩個不同的值,應該被正常新增到集合中,但是卻被拒絕了,因為add時,使用到了方法compareTo,去比較插入的值是否存在,而根據返回結果為0,這樣二者就被認為是相同的值。所以我們一再強調:為避免這種異常,自然排序要和equals結果保持一致,必須滿足e1.compareTo(e2) == 0的和e1.equals(e2)始終有相同的返回值)
  • 實質上,所有實現了Comparable介面的java核心類,都滿足自然排序的要求。唯一的例外類是:BigDecimal類。它的自然排序要求是:值相等而精度是不等的。所以,精度不同但值相同的兩個BigDecimal物件,它們的equals方法返回值應該為true,而compareTo()方法應該返回0:
public static void main(String[] args){
    BigDecimal a = new BigDecimal(2.30);
    BigDecimal b = new BigDecimal(2.3);
    System.out.println("a.eauals(b): " + a.equals(b));
    System.out.println("a.compareTo(b): " + a.compareTo(b));
}

----------------outpout----------------
a.eauals(b): true
a.compareTo(b): 0
複製程式碼

方法

Comparable介面唯一的方法定義如下:

public int compareTo(T o);
複製程式碼

對於a.compareTo(b)返回值定義如下:

  1. a > b => 整數
  2. a == b => 0
  3. a < b => 負數

異常情況:

  1. NullPointException: 入參為null
  2. ClassCastException: 入參型別與被比較型別不相容,如執行new Integer(1).compareTo(new Double(2.1));時,編譯錯誤:Error:(35, 23) java: 不相容的型別: java.lang.Double無法轉換為java.lang.Integer

看下Integer類實現的compareTo方法和equals方法:

public final class Integer extends Number implements Comparable<Integer> { // 實現Comparable介面時,加上泛型限定,實現編譯階段入參型別檢查
    ...
    @Override
    public int compareTo(Integer anotherInteger){
        return (this.vaule < anotherInteger.value) ? -1
            : ((this.value == anotherInteger.value) ? 0 : 1);
    }
    
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }
    ...
}
複製程式碼

參考

JDK8中的Comparable介面原始碼分析

相關文章