1. 程式人生 > >Java中的Comparable接口和Comparator接口

Java中的Comparable接口和Comparator接口

src ignore 原因 vpd 以及 byte 正數 != err

技術分享圖片

  1. 介紹
    Comparable<T>接口和Comparator<T>接口都是JDK中提供的和比較相關的接口。使用它們可以對對象進行比較大小,排序等操作。這算是之後排序的先導知識吧。
    Comparable, 字面意思是“可以比較的”,所以實現它的類的多個實例應該可以相互比較“大小”或者“高低”等等。
    Comparator, 字面意思是“比較儀,比較器”, 它應該是專門用來比較用的“工具”。
  2. Comparable
    Comparable<T>接口

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

    首先看看JDK中怎麽說的:

    This interface imposes a total ordering on the objects of each class that implements it. This ordering is referred to as the class‘s <i>natural ordering</i>, and the class‘s <tt>compareTo</tt> method is referred to as its <i>natural comparison method</i>.<p>

大意是: 任何實現這個接口的類,其多個實例能以固定的次序進行排列。次序具體由接口中的方法compareTo方法決定。

Lists (and arrays) of objects that implement this interface can be sorted automatically by {@link Collections#sort(List) Collections.sort} (and {@link Arrays#sort(Object[]) Arrays.sort}).

如果某個類實現了這個接口,則它的List或數組都能使用Collections.sort()或Arrays.sort()進行排序。

常見的類如Integer, Double, String都實現了此類。一會兒會結合源碼進行分析。

我們先來看Integer中的實現:

public final class Integer extends Number implements Comparable<Integer> {

    private final int value;

    public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }
    public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }
    public static int compareUnsigned(int x, int y) {
        return compare(x + MIN_VALUE, y + MIN_VALUE);
    }

我們只貼出了和比較相關的方法。
可以看到,compareTo方法其中調用了compare方法,這是JDK1.7增加的方法。在Integer中新增這個方法是為了減少不必要的自動裝箱拆箱。傳入compare方法的是兩個Integer的值x和y。
如果x < y, 返回-1;如果x = y, 返回0;如果x > y, 返回1。
順便一說,JDK中的實現非常簡潔,只有一行代碼, 當判斷情況有三種時,使用這種嵌套的判斷 x ? a : b 可以簡潔不少,這是該學習的。
後面的compareUnsigned是JDK1.8新加入的方法, 用來比較無符號數。這裏的無符號數意思是默認二進制最高位不再作為符號位,而是計入數的大小。
其實現是

  public static int compareUnsigned(int x, int y) {
        return compare(x + MIN_VALUE, y + MIN_VALUE);
    }

直接為每個值加了Integer的最小值 -231。我們知道Java中int類型為4個字節,共32位。符號位占用一位的話,則其範圍為-231 到231 - 1。
使用此方法時,所有正數都比負數小。最大值為 -1,因為 -1的二進制所有位均為 1。
也就是1111 1111 1111 1111 1111 1111 1111 1111 > 其它任何32位數。
具體是什麽情況呢?
2.1 計算機編碼
首先我們知道,在計算機中,所有數都是以二進制存在,也就是0和1的組合。
為了使數字在計算機中運算不出錯,出現了原碼,反碼和補碼。原碼就是一個數的二進制表示,其中最高位為符號位,表示其正負。
正數的原碼反碼補碼都一樣,負數的反碼是除符號位以外全部取反,補碼為反碼加1,如圖所示為32位bits(也就是4比特bytes)數的原碼反碼和補碼。

技術分享圖片
為什麽要使用反碼和補碼呢?用四位二進制數舉例:
1的二進制為0001,-1的二進制為1001,如果直接相加,則1 + (-1) = 0,二進制表示為0001 + 1001 = 1010 != 0,所以不能直接使用原碼做運算。
後來出現了反碼,除符號位之外其他位取反,1001(-1)取反後為1110, 現在做加法 0001 (1) + 1110 (-1) = 1111 。由於1111是負數,所以取反之後才是其真實值,取反後為1000,也就是-0。這能滿足條件了,但是美中不足的是,0帶了負號。唯一的問題其實就出現在0這個特殊的數值上。 雖然人們理解上+0和-0是一樣的, 但是0帶符號是沒有任何意義的。 而且會有0000原和1000原兩個編碼表示0。怎麽辦呢?
人們又想出了補碼,它是反碼加1。-1的補碼是 1111,以上的運算用補碼表示就是0001 (1) + 1111 (-1) = 0000 = 0。神奇的發現,這個式子完美契合了十進制加法!
同時我們留出了1000,可以用它表示-8
(-1) + (-7) = (補碼) 1111 + 1001 = 1000 = -8。註意,由於此處的-8使用了之前-0的補碼來表示,所以-8沒有沒有原碼和反碼表示(針對的四位,如果是八位,則沒有原碼和反碼的是-128,依次類推)。
使用補碼, 不僅僅修復了0的符號以及存在兩個編碼的問題, 而且還能夠多表示一個最低數. 這就是為什麽4位二進制, 使用原碼或反碼表示的範圍為[-7, +7], 而使用補碼表示的範圍為[-8, 7].

技術分享圖片
這就是簡單的要用反碼和補碼的原因。
2.2 大數溢出問題
int類型在32位系統中占4個字節、32bit,補碼表示的的數據範圍為:
[10000000 00000000 00000000 00000000] ~ [01111111 11111111 11111111 11111111]
[?231,231?1]
[-2147483648, 2147483647]
在java中表示為:
[Integer.MIN_VALUE, Integer.MAX_VALUE]
與byte類型的表示一樣,由於負數比正數多表示了一個數字。對下限去相反數後的數值會超過上限值,溢出到下限,因此下限的相反數與下限相等;對上限去相反數的數值為負值,該負值比下限的負值大1,在可以表示的範圍內,因此上限的相反數是上限直接取負值。
2.3 String類型的compareTo方法
看完Integer後,我們再來看String中compareTo的實現方式:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];     // String的值

    public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);     // limit, 表示兩個String中長度較小的String長度
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;     // 如果char不相同,則取其差值
            }
            k++;    // 如果char值相同,則繼續往後比較
        }
        return len1 - len2;     // 如果所有0 ~ (lim - 1)的char均相同,則比較兩個String的長短
    }
  // 字面意思是對大小寫不敏感的比較器
    public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                         = new CaseInsensitiveComparator();
    private static class CaseInsensitiveComparator
            implements Comparator<String>, java.io.Serializable {
        private static final long serialVersionUID = 8575799808933029326L;

        public int compare(String s1, String s2) {  
            int n1 = s1.length();
            int n2 = s2.length();
            int min = Math.min(n1, n2);     // 和上面類似,均是取兩個String間的最短長度
            for (int i = 0; i < min; i++) {
                char c1 = s1.charAt(i);
                char c2 = s2.charAt(i);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1); // 統一換成大寫
                    c2 = Character.toUpperCase(c2); // 統一換成大寫
                    if (c1 != c2) {     // 大寫如果不相等則再換為小寫試試
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {     // 到此處則確定不相等
                            // No overflow because of numeric promotion
                            return c1 - c2;
                        }
                    }
                }
            }
            return n1 - n2;
        }

        /** Replaces the de-serialized object. */
        private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
    }
    // String的方法,可以直接使用這個方法和其它String進行比較,
    // 內部實現是調用內部比較器的compare方法
    public int compareToIgnoreCase(String str) {
        return CASE_INSENSITIVE_ORDER.compare(this, str);
    }   
}

String中的關於compare的方法相對復雜一點,但還是比較簡單。我們先不看其他的代碼,只重點關註compareTo方法。

public int compareTo(String anotherString) {
        int len1 = value.length;
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2);     // limit, 表示兩個String中長度較小的String長度
        char v1[] = value;
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) {
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {
                return c1 - c2;     // 如果char不相同,則取其差值
            }
            k++;    // 如果char值相同,則繼續往後比較
        }
        return len1 - len2;     // 如果所有0 ~ (lim - 1)的char均相同,則比較兩個String的長短
    }

內容很簡潔,就是取兩個String的長度中較小的,作為限定值(lim)。之後對數組下標為從0到lim - 1的char變量進行遍歷比較,如果遇到不相同的值,返回其差值。一般我們只用其正負性,如果返回負數則說明第一個對象比第二個對象“小”。
例如比較 "abc"和"bcd",當對各自第一個字符‘a‘和 ‘b‘進行比較時,發現 ‘a‘ != ‘b‘,則返回 ‘a‘ - ‘b‘ ,這個值是負數, char類型的-1,Java會自動將其類型強轉為int型。最後得出結論"abc"比"bcd"小。

  1. Comparator
    Comparator<T>接口

    public interface Comparator<T> {
    int compare(T o1, T o2);
    }

    這是一個外部排序接口,它的功能是規定“比較大小”的方式。實現它的類可以作為參數傳入Collections.sort()或Arrays.sort(),使用它的比較方式進行排序。
    它可以為沒有實現Comparable接口的類提供排序方式。
    String類中以及Array類等都有實現此接口的內部類。
    在上面String的源碼中就有一個內部的自定義Comparator類CaseInsensitiveComparator, 我們看看它的源碼。

    public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                         = new CaseInsensitiveComparator();
    private static class CaseInsensitiveComparator
            implements Comparator<String>, java.io.Serializable {
        private static final long serialVersionUID = 8575799808933029326L;
    
        public int compare(String s1, String s2) {  
            int n1 = s1.length();
            int n2 = s2.length();
            int min = Math.min(n1, n2);     // 和上面類似,均是取兩個String間的最短長度
            for (int i = 0; i < min; i++) {
                char c1 = s1.charAt(i);
                char c2 = s2.charAt(i);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1); // 統一換成大寫
                    c2 = Character.toUpperCase(c2); // 統一換成大寫
                    if (c1 != c2) {     // 大寫如果不相等則再換為小寫試試
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {     // 到此處則確定不相等
                            // No overflow because of numeric promotion
                            return c1 - c2;
                        }
                    }
                }
            }
            return n1 - n2;
        }
    
        /** Replaces the de-serialized object. */
        private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
    }
    // String的方法,可以直接使用這個方法和其它String進行比較,
    // 內部實現是調用內部比較器的compare方法
    public int compareToIgnoreCase(String str) {
        return CASE_INSENSITIVE_ORDER.compare(this, str);
    }   
    }

CaseInsensitiveComparator, 字面意思是對大小寫不敏感的比較器。
我們觀察它的compare方法,可以發現,它和上面的compareTo方法實現類似,都是取兩個String中長度較小的,作為限定值min,之後對數組下標為從0到min - 1的char變量進行遍歷比較。和上面稍有不同的是,此處先將char字符統一換成大寫(upper case), 如果仍然不相等,再將其換為小寫(lower case)比較。一個字母只有大寫或者小寫兩種情形,如果這兩種情況都不想等則確定不相等,返回其差值。如果限定值內所有的char都相等的話,再去比較兩個String類型的長度。
例如比較 "abC"和"ABc",compareTo會直接返回 ‘a‘ - ‘A‘,而compareToIgnoreCase方法由於使用了CaseInsensitiveComparator,比較結果最終會返回true。

Java中的Comparable接口和Comparator接口