一個 List l 可能被做如下排序:
Collections.sort(l);
如果這個 list 由 String 元素所組成,
那么它將按詞典排序法(按字母順序)進行排序; 如果它是由 Date 元素所組成, 那么它將按年代順序來排序。 Java 怎么會知道該怎么做呢? 這一定是個魔術!
其實不然。實際上, String 和 Date 均實現了Comparable接口。 Comparable 接口為一個類提供一個 自然排序( natural
ordering), 它允許那個類的對象被自動排序。下表列出了實現了Comparable 的JDK類:
類 自然排序
Byte
帶符號的數字排序
Character 不帶符號的數字排序
Long 帶符號的數字排序
Integer
帶符號的數字排序
Short 帶符號的數字排序
Double 帶符號的數字排序
Float
帶符號的數字排序
BigInteger 帶符號的數字排序
BigDecimal 帶符號的數字排序
File
依賴系統的按路徑名字母順序排序
String 按字母順序排序
Date 按年代順序排序
CollationKey
特定字符集按字母順序排序
如果你要為一個其元素沒有實現
Comparable的列表排序,Collections.sort(list) 將扔出一個
ClassCastException。類似的,如果你要為一個其元素沒有作相互比較的列表進行排序, Collections.sort 將扔出一個
ClassCastException. 能夠被相互比較的元素被稱作 mutually comparable(可相互比較的)。
雖然不同類型的元素有可能被相互比較,但以上列出的任何JDK類型都不允許在類之間的比較 (inter-class comparison)。
如果你只是要為可比較的元素的列表進行排序,或為它們創建排序的對象集, 則這就是你實際需要了解的全部有關 Comparable
接口的內容。如果你要實現你自己的 Comparable 類型,則下一節將會引起你的興趣。
編寫你自己的 Comparable 類型
Comparable 接口由一個單一的方法構成:
public interface Comparable
{
public int compareTo(Object o);
}
compareTo
方法將接收對象與特定對象進行比較,并在接收對象小于、等于或大于特定對象時分別返回負整數、空或一個正整數。如果特定對象不能與接收對象相比較,該方法扔出一個ClassCastException.
這是一個表示某人姓名的類(a class representing a person´s name), 它實現了 Comparable:
import java.util.*;
public class Name implements
Comparable {
private String firstName, lastName;
public
Name(String firstName, String lastName) {
if (firstName==null ||
lastName==null)
throw new NullPointerException();
this.firstName =
firstName;
this.lastName = lastName;
}
public
String firstName() {return firstName;}
public String lastName() {return
lastName;}
public boolean equals(Object o) {
if (!(o
instanceof Name))
return false;
Name n = (Name)o;
return
n.firstName.equals(firstName)
&&
n.lastName.equals(lastName);
}
public
int hashCode() {
return 31*firstName.hashCode() +
lastName.hashCode();
}
public String toString() {return
firstName + " " + lastName;}
public int compareTo(Object o)
{
Name n = (Name)o;
int lastCmp =
lastName.compareTo(n.lastName);
return (lastCmp!=0 ? lastCmp
:
firstName.compareTo(n.firstName));
}
}
為了使這個例子短一些,該類受到了一點限制:它不支持中間名,它要求必須同時具有first
name 和 last name, 而這不是在全世界都通用的。盡管如此,這個例子仍有幾個重要之處:
Name 對象是不變的(
immutable)。作為相等、不變類型的所有其它事情就是如何做的問題,特別是對那些將被用來作為 Sets 中的元素或 Maps
中的鍵的對象來說,更是如此。如果你對這些 對象集 中的元素或鍵做了更改,這些 對象集 將中斷。
構造函數可檢查它的參數是否為 null。
這可以保證所有的Name 對象都能很好地形成。因而沒有其它方法會扔出NullPointerException.
hashCode
方法被重新定義。對重新定義 equals 方法的任意類來說,這是必需的(essential)。 一般約定(general contract)需要
Object.equals. (Equal 對象必須具有相等的哈希代碼) 。
如果特定對象為 null,或一個不適當的類型,
equals 方法則返回 false。 在這種情況下, compareTo 方法扔出一個運行時異常。這兩個行為都是各自方法的一般約定所必需的。
toString 方法已被重新定義,從而可以以人們能夠讀懂的形式打印 Name 。這總是一個好主意,特別是對要被放入對象集
中的對象來說,更有益處。各種 對象集 類型的 toString 方法依賴它們的元素、鍵和值的 toString 方法。
由于這一節介紹的是有關元素排序的問題,因而讓我們稍微多談一點 Name 的 compareTo 方法。它實現標準的姓名-排序算法,在該算法中,last
name 優先于 first name。這恰恰是你在一個natural ordering(自然排序)中所想要的。 如果自然排序不自然,那才容易引起混亂呢!
請看 compareTo 是如何被實現的,因為它是相當典型的。首先,你將 Object 參數轉換為適當類型;
如果參數類型是不適當的,則會扔出一個適當的異常(ClassCastException);那么你應該比較對象的最重要部分(在此案例中為 last
name)。通常,你可以使用該部分的類型的自然排序。 在次案例中,該部分是一個 String,
并且自然的(按詞典順序的)排序正是所要求的。如果比較的結果是空(它表示等同性)之外的其它東西,你就做完了:你可以返回結果。
如果最重要的部分是相等的,你就應該繼續比較次重要部分。在此案例中,只有兩個部分 (first name and last name)。
如果有更多的部分,你就應該以顯而易見的方式繼續進行,直到發現兩個不相等的部分(否則你就應該比較最不重要的部分),這時,你就可以返回比較結果了。 這是 一個建立
Name 對象列表并對它們進行排序的小程序:
import java.util.*;
class NameSort
{
public static void main(String args[]) {
Name n[] = {
new
Name("John", "Lennon"),
new Name("Karl", "Marx"),
new
Name("Groucho", "Marx"),
new Name("Oscar",
"Grouch")
};
List l =
Arrays.asList(n);
Collections.sort(l);
System.out.println(l);
}
}
如果你運行這個程序,以下是它所打印的結果:
[Oscar Grouch, John Lennon, Groucho Marx, Karl Marx]對 compareTo
方法的行為有四個限制,我們現在不作一一討論,因為它們的技術性太強,并且十分枯燥,我們最好將其留在API文本中。但是,所有實現 Comparable
的類都必須接受這些限制的約束,這一點是確實重要的。因此,如果你要編寫一個實現Comparable 的類,請讀那些有關 Comparable
的文本吧。要試圖為違反了那些限制的對象的列表進行排序可能引發不可預知的行為。從技術上講,這些限制保證了自然排序是實現它的類的對象的部分順序(partial
order)。保證排序被很好地定義是十分必要的。
比較器(Comparators)
好,到目前為止,你已經了解了自然排序。那么,如果要對某些對象不按自然順序進行排序,又會怎么樣呢?或者,如果你要為某些不實現
Comparable 的對象進行排序呢?為做這些事情,你需要提供一個Comparator。 Comparator 實際就是一個封裝了排序的對象。與
Comparable 接口類似,Comparator 接口由一個的方法構成:
public interface Comparator {
int compare(Object o1, Object o2);}
compare
方法比較它的兩個參數,當第一個參數小于、等于或大于第二個參數時,分別返回一個負整數、空或正整數。如果其中一個參數具有對 Comparator
不適合的類型,compare 方法則扔出一個 ClassCastException。
在上一節中的有關 Comparable
的許多內容也適用Comparator。編寫一個 compare 方法幾乎等同于編寫一個compareTo 方法,除前者是把兩個參數都當作參數之外。compare
方法必須象Comparable 的 compareTo 方法一樣,服從同樣的四個"技術限制"。出于同樣的原因, Comparator
必須對它所比較的對象誘發一個 partial order(部分順序)。
假設你有一個稱作 EmployeeRecord 的類:
public class EmployeeRecord implements Comparable { public Name
name(); public int employeeNumber(); public Date hireDate();
...}
我們假設 EmployeeRecord 對象的自然排序是對雇員姓名的排序
(就象上一個例子中所定義的)。不幸的是,老板要求我們提出一個按雇員資歷排序的列表。這就意味著我們必須做些額外的工作,但是不多。以下是一個將生成所需列表的程序:
import java.util.*;class EmpSort { static final Comparator
SENIORITY_ORDER = new Comparator() { public int compare(Object o1, Object o2) {
EmployeeRecord r1 = (EmployeeRecord) o1; EmployeeRecord r2 = (EmployeeRecord)
o2; return r2.hireDate().compareTo(r1.hireDate()); } }; static final Collection
employees = ... ; // Employee Database public static void main(String args[]) {
List emp = new ArrayList(employees); Collections.sort(emp, SENIORITY_ORDER);
System.out.println(emp); }}
以上程序中的 Comparator
相當簡單。它將它的參數轉換為EmployeeRecord, 并依賴適用于 hireDate()方法的 Date 的自然排序。請注意:Comparator
將它的第二個參數的雇用-日期傳遞給第一個參數,而不是按反方向傳遞。
這是因為,最新雇用的雇員資歷最淺:按照雇用-日期排序將使列表成為反向資歷-順序。另一個獲得相同結果的方法是:保持參數順序,但對比較結果求反。
return -r1.hireDate().compareTo(r2.hireDate());
兩種方法同樣可取。使用哪一種,全由你自己。
以上程序中的 Comparator ,在對 List
進行排序時,效果很好。但有一個小的缺陷:它不能被用來對一個排序的 對象集 (如TreeSetM) 進行排序,因為它生成一個嚴格的部分(strictly
partial) 排序。這意味著這個comparator 使不相等的對象相等。特別的,它會使任意兩個雇用日期相同的雇員成為相等。當你為一個 List
排序時,這沒關系,但當你使用 Comparator 為一個sort排序的對象集 排序時,
這就是致命的了。如果你將多個雇用日期相同的雇員用Comparator插入到一個TreeSet之中,那么只有第一個將被添加到
set,第二個將被作為一個復制元素而忽略。
為解決這個問題,你必須做的一切就是修整 Comparator 使之生成一個 total
ordering(完整排序)。 換句話說,修整 Comparator 是為了使在使用compare 時被認為相等的唯一元素即是那些在使用equals
時被認為相等的元素。 實現這個目的的途徑是做一個兩部分(two-part)比較 (就象我們為 Name
做的那樣),這里的第一部分是我們真正感興趣的(此案例中為雇用-日期),而第二部分是可唯一識別的對象屬性。在此案例中,雇員號是作為第二部分使用的明顯的屬性。請看下面的
Comparator :
static final Comparator SENIORITY_ORDER = new
Comparator() { public int compare(Object o1, Object o2) { EmployeeRecord r1 =
(EmployeeRecord) o1; EmployeeRecord r2 = (EmployeeRecord) o2; int dateCmp =
r2.hireDate().compareTo(r1.hireDate()); if (dateCmp != 0) return dateCmp; return
(r1.employeeNumber() < r2.employeeNumber() ? -1 : (r1.employeeNumber() ==
r2.employeeNumber() ? 0 : 1)); }};
最后注意一點,你可能被引誘用更簡單的程序來替代 Comparator
中最后的 return 語句:
return r1.employeeNumber() - r2.employeeNumber();
不要這樣做,除非你能絕對保證不出現一個負的雇員數!這個技巧不可普遍使用,因為一個帶正負號的整數類型,即使再大,也不足以表示兩個任意的帶正負號的整數的差值。如果
i 是一個大的正整數,j 是一個大的負整數,i-j 將溢出并返回一個負整數。 Comparator
的結果違反了我們一直在講的四個技術限制之中的一個限制(傳遞性),并導致可怕而玄妙的故障。 這并不是一個純技術問題;搞不好,它會傷著你。 [@more@]
網站欄目:深入理解CollectionsAPI(轉)-創新互聯
轉載來于:http://newbst.com/article42/dihjec.html
成都網站建設公司_創新互聯,為您提供網站導航、App開發、網站設計、網站策劃、網站排名、關鍵詞優化
廣告
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源:
創新互聯