List
l
能夠以下排序。html
Collections.sort(l);
若是List
包含String
元素,它將按字母順序排序,若是它由Date
元素組成,它將按時間順序排序,這是怎麼發生的?String
和Date
都實現了Comparable接口,Comparable
實現爲類提供了天然的順序,容許該類的對象自動排序,下表總結了一些實現Comparable
的更重要的Java平臺類。java
類 | 天然排序 |
---|---|
Byte |
有符號數字 |
Character |
無符號數字 |
Long |
有符號數字 |
Integer |
有符號數字 |
Short |
有符號數字 |
Double |
有符號數字 |
Float |
有符號數字 |
BigInteger |
有符號數字 |
BigDecimal |
有符號數字 |
Boolean |
Boolean.FALSE < Boolean.TRUE |
File |
依賴於系統的路徑名稱上的字典 |
String |
接字母順序 |
Date |
按時間順序 |
CollationKey |
特定於語言環境的字典 |
若是你嘗試對列表進行排序,其中的元素未實現Comparable
,Collections.sort(list)
將拋出ClassCastException,相似地,若是你嘗試使用comparator
對其元素沒法相互比較的列表進行排序,則Collections.sort(list, comparator)
將拋出ClassCastException
。雖然不一樣類型的元素能夠相互比較,但這裏列出的類別都不容許進行類間比較。git
若是你只想對可比較元素的列表進行排序或建立它們的已排序集合,那麼你真正須要瞭解Comparable
接口的全部內容,若是要實現本身的Comparable
類型,下一部分將是你感興趣的。github
Comparable
接口包含如下方法。算法
public interface Comparable<T> { public int compareTo(T o); }
compareTo
方法將接收對象與指定對象進行比較,並返回負整數、0或正整數,具體取決於接收對象是否小於、等於或大於指定對象,若是沒法將指定的對象與接收對象進行比較,則該方法將拋出ClassCastException
。segmentfault
如下表示人名的類實現了Comparable
。api
import java.util.*; public class Name implements Comparable<Name> { private final 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(Name n) { int lastCmp = lastName.compareTo(n.lastName); return (lastCmp != 0 ? lastCmp : firstName.compareTo(n.firstName)); } }
爲了使前面的例子簡短,該類有些限制:它不支持中間名,它要求名字和姓氏,而且它不以任何方式國際化,儘管如此,它還說明了如下要點:oracle
Name
對象是不可變的,在全部其餘條件相同的狀況下,不可變類型是解決問題的方法,特別是對於將做爲集合中的元素或Map
中的鍵使用的對象,若是你在集合中修改元素或鍵,這些集合將會中斷。null
,這能夠確保全部Name
對象都格式正確,這樣其餘任何方法都不會拋出NullPointerException
。hashCode
方法被從新定義,這對於從新定義equals
方法的任何類都是必不可少的(等同對象必須具備相同的哈希碼)。null
或類型不合適,則equals
方法返回false
,compareTo
方法在這些狀況下拋出運行時異常,這兩種行爲都是各自方法的通常契約所要求的。toString
方法已從新定義,所以它以人類可讀的形式打印Name
,這老是一個好主意,特別是對於要放入集合的對象,各類集合類型的toString
方法依賴於其元素、鍵和值的toString
方法。因爲本節是關於元素排序的,讓咱們再談談Name
的compareTo
方法,它實現了標準的名稱排序算法,其中姓氏優先於名字,這正是你想要的天然順序,若是天然順序不天然,那將會很是混亂!函數
看看compareTo
是如何實現的,由於它很是經典,首先,比較對象的最重要部分(在本例中爲姓氏),一般,你能夠只使用部分類型的天然順序,在這種狀況下,該部分是一個字符串,天然(詞典)排序正是所要求的。若是比較的結果不是0(表明相等),那麼就完成了:你只需返回結果。若是最重要的部分相同,則繼續比較下一個最重要的部分,在這種狀況下,只有兩個部分 — 名字和姓氏。若是有更多的部分,你會以明顯的方式進行,比較部分,直到你發現兩個不相等或你正在比較最不重要的部分,此時你將返回比較的結果。this
爲了說明這一切都是有效的,這裏有一個程序,它構建了一個名稱列表並對它們進行排序。
import java.util.*; public class NameSort { public static void main(String[] args) { Name[] nameArray = { new Name("John", "Smith"), new Name("Karl", "Ng"), new Name("Jeff", "Smith"), new Name("Tom", "Rich") }; List<Name> names = Arrays.asList(nameArray); Collections.sort(names); System.out.println(names); } }
若是你運行這個程序,這是它打印的內容。
[Karl Ng, Tom Rich, Jeff Smith, John Smith]
compareTo
方法的行爲有四個限制,咱們如今不會討論它們,由於它們至關技術性和枯燥,最好留在API文檔中,實現Comparable
的全部類都遵照這些限制很是重要,所以若是你正在編寫實現它的類,請閱讀Comparable
的文檔。嘗試對違反限制的對象列表進行排序具備未定義的行爲,從技術上講,這些限制確保天然順序是實現它的類的對象的總順序,這對於確保明肯定義排序是必要的。
若是你想按一些對象的天然順序之外的順序排序,該怎麼辦?或者,若是要對某些未實現Comparable
的對象進行排序,該怎麼辦?要執行上述任一操做,你須要提供Comparator — 一個封裝排序的對象,與Comparable
接口同樣,Comparator
接口由單個方法組成。
public interface Comparator<T> { int compare(T o1, T o2); }
compare
方法比較它的兩個參數,返回一個負整數、0或一個正整數,具體取決於第一個參數是小於、等於仍是大於第二個參數,若是其中一個參數的Comparator
類型不合適,則compare
方法將拋出ClassCastException
。
關於Comparable
的大部份內容也適用於Comparator
,編寫compare
方法與編寫compareTo
方法幾乎徹底相同,只是前者將兩個對象做爲參數傳入,因爲一樣的緣由,compare
方法必須遵照與Comparable
的compareTo
方法相同的四個技術限制 — Comparator
必須對它所比較的對象產生總順序。
假設你有一個名爲Employee
的類,以下所示。
public class Employee implements Comparable<Employee> { public Name name() { ... } public int number() { ... } public Date hireDate() { ... } ... }
讓咱們假設Employee
實例的天然順序是員工姓名上的Name
排序(如上例所定義),不幸的是,老闆要求按照資歷順序列出員工名單。這意味着咱們必須作一些工做,但並很少,如下程序將生成所需的列表。
import java.util.*; public class EmpSort { static final Comparator<Employee> SENIORITY_ORDER = new Comparator<Employee>() { public int compare(Employee e1, Employee e2) { return e2.hireDate().compareTo(e1.hireDate()); } }; // Employee database static final Collection<Employee> employees = ... ; public static void main(String[] args) { List<Employee> e = new ArrayList<Employee>(employees); Collections.sort(e, SENIORITY_ORDER); System.out.println(e); } }
程序中的Comparator
至關簡單,它依賴於應用於hireDate
訪問器方法返回的值的Date
的天然順序,注意,Comparator
將第二個參數的僱用日期傳遞給第一個參數,而不是反過來,緣由是最近招聘的員工級別最低,按僱用日期順序排序會使該名單的資歷順序相反,人們有時用來達到這種效果的另外一種技術是保持參數順序,但要否認比較的結果。
// Don't do this!! return -r1.hireDate().compareTo(r2.hireDate());
你應該老是使用前一種技術來支持後者,由於後者不能保證有效,這樣作的緣由是compareTo
方法能夠返回任何負整數,若是它的參數小於調用它的對象。有一個負整型數在被否認時仍然是負的,儘管這看起來很奇怪。
-Integer.MIN_VALUE == Integer.MIN_VALUE
上一個程序中的Comparator
能夠很好地對List
進行排序,但確實存在一個缺陷:它不能用於排序已排序的集合,例如TreeSet
,由於它生成的順序與equals
不兼容,這意味着這個Comparator
至關於equals
方法所沒有的對象。特別是,在同一天僱傭的任何兩名員工將相等,當你對List
進行排序時,這並不重要,可是當你使用Comparator
來排序一個已排序的集合時,它是致命的,若是你使用此Comparator
將在同一日期僱用的多名員工插入到TreeSet
中,則只會將第一個員工添加到該集合中,第二個將被視爲重複元素,將被忽略。
要解決此問題,只需調整Comparator
,以便生成與equals
兼容的排序,換句話說,調整它以便在使用compare
時看到相同的惟一元素是那些在使用equals
進行比較時也被視爲相等的元素。執行此操做的方法是執行兩部分比較(像對於Name
),其中第一部分是咱們感興趣的部分 — 在這種狀況下,是僱用日期 — 第二部分是惟一標識對象的屬性,員工編號在這裏是明顯的屬性,這是比較器的結果。
static final Comparator<Employee> SENIORITY_ORDER = new Comparator<Employee>() { public int compare(Employee e1, Employee e2) { int dateCmp = e2.hireDate().compareTo(e1.hireDate()); if (dateCmp != 0) return dateCmp; return (e1.number() < e2.number() ? -1 : (e1.number() == e2.number() ? 0 : 1)); } };
最後一點:你可能想要使用更簡單的方法替換Comparator
中的最終return
語句:
return e1.number() - e2.number();
除非你絕對肯定沒有人會有負的員工編號,不然不要這樣作!這個技巧一般不起做用,由於帶符號整數類型不夠大,不能表示兩個任意帶符號整數的差,若是i
是一個大的正整數且j
是一個大的負整數,i - j
將溢出並返回一個負整數,由此產生的comparator
違反了咱們一直在討論的四個技術限制之一(傳遞性)併產生可怕的、微妙的錯誤,這不是純粹的理論問題。