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