1. 程式人生 > >Comparable 與 Comparator 兩個介面的作用

Comparable 與 Comparator 兩個介面的作用

在我的印象裡,對於陣列排序,最簡單的方法就是使用Arrays.sort(陣列a);它也使我理所應當的認為:Arrays這個類完全實現這個sort的方法,而實際上並非如此,先看下sort的原型:public static <T extends Comparable<? super T>> void sort(List<T> list),藍色部分即是說你要排序的這個集合元素已經實現了介面Comparable,等等,我好像沒有實現任何Comparable介面,事實上,String類和所有的包裝類物件在java API中已經實現了這個介面。參考API:

      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

o),絕不會是Object; 只有一個引數,而Comparator介面卻有兩個引數:int compare(T o1, T o2);

     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結果。