Java中的Comparable接口和Comparator接口

  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。

                                             大家記得點贊關注一下   有好的想法或者建議歡迎留言

                                                         動動手指掃描二維碼關注一下我

              

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章