Comparable 與 Comparator 兩個介面的作用
public final class String extends implements , <>,
如果你嘗試為沒有實現Comparable介面的元素排序,編譯器會出錯:The generic method sort(List<T>) of type Collections is not applicable for the arguments(ArrayList<你剛編寫的類>). The inferred type 你剛編寫的類is not a valid substitute for the bounded parameter <T extends Comparable<? super T>>。
此介面強行對實現它的每個類的物件進行整體排序。此排序被稱為該類的自然排序,類的 compareTo 方法被稱為它的自然比較方法。實現此介面的物件列表(和陣列)可以通過Collections.sort(和 Arrays.sort)進行自動排序。實現此介面的物件可以用作有序對映表中的鍵或有序集合中的元素,無需指定比較器。對於類C的每一個 e1 和 e2 來說,當且僅當 (e1.compareTo((Object)e2) == 0) 與 e1.equals((Object)e2) 具有相同的布林值時,類 C 的自然排序才叫做與 equals 一致。注意,null 不是任何類的例項,即使 e.equals(null) 返回 false,e.compareTo(null) 也會丟擲 NullPointerException。強烈推薦(雖然不是必需的)使自然排序與 equals 一致。這是因為在使用其自然排序與 equals 不一致的元素(或鍵)時,沒有顯式比較器的有序集合(和有序對映表)行為表現“怪異”。尤其是,這樣的有序集合(或有序對映表)違背了根據 equals 方法定義的集合(或對映表)的常規協定。例如,如果將兩個鍵 a 和 b 新增到一個沒有使用顯式比較器的有序集合中,使得 (!a.equals((Object)b) && a.compareTo((Object)b) == 0),則第二個 add 操作返回 false(有序集合的大小沒有增加),因為從有序集合的角度來看,a 和 b 是等效的。 實現了comparable介面的類,其物件可以作為SortedMap的key,或者SortedSet的元素。
我們只能在類中實現compareTo()一次,不可能說經常來修改類的程式碼實現自己想要的排序,因此如果要以不同於compareTo()方法中指定的順序排序我們的類物件,那麼該怎麼辦呢?大家一定注意到Collections.sort()有一個過載版本。 public static <T> void sort(List<T> list, Comparator<? super T> c)根據指定比較器產生的順序對指定列表進行排序。此列表內的所有元素都必須可使用指定比較器相互比較(也就是說,對於列表中的任意 e1 和 e2 元素,c.compare(e1, e2) 不得丟擲 ClassCastException)。此排序被保證是穩定的:不會因呼叫 sort 而對相等的元素進行重新排序。 Comparator<? super T> c 第二個引數是實現了介面Comparator的例項物件。注意這兩者的區別,Comparable 介面的方法:public int compareTo(T
Comparator 可以通過 Collections類的reverseOrder()這個方法強勢返回對Comparable排序的倒轉,如:Arrays.sort(a, Collections.reverseOrder()); 如果是String,就是按照逆字典順序進行排序。
這兩個介面和集合類本身無關,但通常和集合內的元素有關,因為集合的排序要用到它們中的方法。一個類的例項要想實現排序,必須實現Comparable,或者提供相應的Comparator。說清楚這兩者的來歷與大概的作用,以及共同點,那麼再說下他們兩者的區別。只是Comparable(可比較的)是在集合內部定義的方法實現的排序,比如,兩個人要比較身高,分辨高矮是人類固有的能力,兩個人只要站到一起就能分出誰高誰矮。Comparator(比較器)是在集合外部實現的排序。所以,如想實現排序,就需要在集合外定義Comparator介面的方法compare()或在集合內實現Comparable介面的方法compareTo(),Comparable是一個物件本身就已經支援自比較所需要實現的介面(如String Integer自己就可以完成比較大小操作)而Comparator是一個專用的比較器,當這個物件不支援自比較或者自比較函式不能滿足你的要求時,你可以寫一個比較器來完成兩個物件之間大小的比較。還有個地方就是,實現Comparable只能定義一種比較方法,但是有時候會對一個集合進行不同的排序方法,此時就可以提供別各種各樣的Comparator來對集合排序,而對於要排序的元素不需要更改,所以我覺得Comparator提供了更多的靈活性。使用這種策略來比較時,如何進行比較和兩個物件本身無關,而是由第三者(即比較器)來完成的。只要實現Comparator介面,任何一個物件都可能成為一個“比較器”,但比較器並不是比較自己的例項,而是比較另外兩個物件,比較器在這裡充當“仲裁者”的角色,這也就是為什麼compare()方法需要兩個引數。比如,兩個人要比較誰智商更高,靠他們自身無法進行,這時要藉助一個比較器(比如,智商測試題)。那麼先看一個非常簡單易懂的示例:
import java.util.*;
public class Pockets {
public static void main(String[] args) {
String[] sa = {"nickel", "button", "key", "lint"};
Sorter s = new Sorter(); //新建一個排序器
for(String s2: sa){ System.out.print(s2 + " "); }
Arrays.sort(sa, s); //這個排序器s是與sa這個類物件是相關。
System.out.println();
for(String s2:sa){ System.out.print(s2+" "); }
}
static class Sorter implements Comparator<String>{ //注意紅色的部分就是你要去排序的類,如果你文不對題,硬讓這個類去排序一個未知的class Dog的類,會報錯:The method sort(T[], Comparator<? super T>) in the type Arrays is not applicable for the arguments (UU[], Pockets.Sorter)
public int compare(String a, String b){
return a.compareTo(b);
}
}
}
再在更加複雜的概念上理解這個排序機制,在網上找到一個例子,維護一個簡單的員工資料庫,每個員工是一個Employee類的例項。Employee類可定義為:
public class Employee {
private String num;
private String name;
private int age;
private int salary;
public Employee(String num, String name) {
this.num = num;
this.name = name;
}
public void setName(String newNum) {
num = newNum;
}
public void setAge(int newAge) {
age = newAge;
}
public void setSalary(int newSalary) {
salary = newSalary;
}
public String getNum() {
return num;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public int getSalary() {
return salary;
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("Employee Information:");
sb.append("\n");
sb.append("Number:");
sb.append(getNum());
sb.append("\n");
sb.append("Name:");
sb.append(getName());
sb.append("\n");
sb.append("Age:");
sb.append(getAge());
sb.append("\n");
sb.append("Salary:");
sb.append(getSalary());
sb.append("\n");
return sb.toString();
}
}
EmployeeDatabase類建立Employee類的例項,並把它們存入集合:
Java code
import java.util.*;
public class EmployeeDatabase {
public static void main(String[] args) {
List<Employee> allEmployees = new ArrayList<Employee>();
Employee employee1 = new Employee("AAA", "Barack Omaba");
employee1.setAge(50);
employee1.setSalary(9999);
allEmployees.add(employee1);
Employee employee2 = new Employee("BBB", "George Bush");
employee2.setAge(60);
employee2.setSalary(5999);
allEmployees.add(employee2);
System.out.println(allEmployees);
}
}
現在,你需要檢索所有員工,並讓他們按一定順序顯示(比如按年齡遞增),這時需要用到Collections.sort()方法。Collections.sort()有兩種策略:一種是讓集合元素本身實現Comparable介面,另一種是使用使用者提供的比較器(即Comparator)。使用第一種策略時,必須修改元素類的定義,讓它實現Comparable,因此,你必須把Employee類修改為:
public class Employee implements Comparable<Employee> {
public int compareTo(Employee another) {
return getAge() - another.getAge();
}
// 其餘部分不變
}
說明一下,因為compareTo()方法約定:本物件大於另一個物件時,返回大於0的整數,小於時返回小於0的整數,等於時返回0。所以,可以直接返回兩者年齡的差,來實現按年齡比較。這樣就可以在main()方法中使用Collections.sort(allEmployees);來對員工按年齡排序了。但是,這種排序是非常不靈活的:第一,需要修改集合元素類Employee,而很多情況下,我們沒有辦法修改公共的類。第二,沒有辦法實現多種方式排序,如按編號,按姓名,按薪水等等。這時需要使用另一種策略,即Comparator。Comparator使用其compare()方法返回的整數來比較兩個物件,規則和compareTo()一樣。如同樣實現年齡比較,使用Comparator時,無需修改Employee類,可以在排序的時候定義相應的比較器。使用Collections.sort()方法的另一個版本:
Collections.sort(allEmployees, new Comparator<Employee>() {
public int compare(Employee one, Employee another) {
return one.getAge() - another.getAge();
}
});
這裡使用了匿名內部類,實際上相當於先定義一個比較器類,如:
class EmployeeComparator implements Comparator<Employee> {
public int compare(Employee one, Employee another) {
return one.getAge() - another.getAge();
}
}
再使用:
Collections.sort(allEmployees, new EmployeeComparator());
可以看到,比較器完全獨立於元素類Employee,因此可以非常方便地修改排序規則。你還可以定義一系列比較器,供排序時選擇使用,如:
// 按薪水升序
class EmployeeSalaryAscendingComparator implements Comparator<Employee> {
public int compare(Employee one, Employee another) {
return one.getSalary() - another.getSalary();
}
}
// 按薪水降序
class EmployeeSalaryDescendingComparator implements Comparator<Employee> {
public int compare(Employee one, Employee another) {
return another.getSalary() - one.getSalary();
}
}
相應的使用方法如:
Collections.sort(allEmployees, new EmployeeSalaryAscendingComparator());
Collections.sort(allEmployees, new EmployeeSalaryDescendingComparator());
使用Comparator時,元素類無需實現Comparable,因此我們保持最初版本的Employee,但實際應用中,可以用Comparable的compareTo()方法來定義預設排序方式,用Comparator定義其他排序方式。實現其compare(T o1,T o2)有一個約定,就是 a.compare(b) == 0 和a.equals(b)要有相同的boolean結果。