自然排序和強排序

這裏所說到的Java中的排序並不是指插入排序、希爾排序、歸併排序等具體的排序算法。而是指執行這些排序算法時,比較兩個對象“大小”的比較操作。我們很容易理解整型的 i>j 這樣的比較方式,但當我們對多個對象進行排序時,如何比較兩個對象的“大小”呢?這樣的比較 stu1 > stu2 顯然是不可能通過編譯的。爲了解決如何比較兩個對象大小的問題,JDK提供了兩個接口 java.lang.Comparable 和 java.util.Comparator 。 

一、自然排序:java.lang.Comparable 
  Comparable 接口中只提供了一個方法: compareTo(Object obj) ,該方法的返回值是 int 。如果返回值爲正數,則表示當前對象(調用該方法的對象)比 obj 對象“大”;反之“小”;如果爲零的話,則表示兩對象相等。下面是一個實現了 Comparable 接口的 Student 類: 

Java代碼  收藏代碼
  1. public class Student implements Comparable {  
  2.   
  3.     private int id;  
  4.       
  5.     private String name;  
  6.   
  7.     public Student() {  
  8.         super();  
  9.     }  
  10.   
  11.     @Override  
  12.     public int compareTo(Object obj) {  
  13.         if (obj instanceof Student) {  
  14.             Student stu = (Student) obj;  
  15.             return id - stu.id;  
  16.         }  
  17.         return 0;  
  18.     }  
  19.   
  20.     @Override  
  21.     public String toString() {  
  22.         return "<" + id + ", " + name + ">";  
  23.     }  
  24. }  

  Student 實現了自然排序接口 Comparable ,那麼我們是怎麼利用這個接口對一組 Student 對象進行排序的呢?我們在學習數組的時候,使用了一個類來給整型數組排序: java.util.Arrays 。我們使用 Arrays 的 sort 方法來給整型數組排序。翻翻 API 文檔就會發現, Arrays 裏給出了 sort 方法很多重載形式,其中就包括 sort(Object[] obj) ,也就是說 Arryas 也能對對象數組進行排序,排序過程中比較兩個對象“大小”時使用的就是 Comparable 接口的 compareTo 方法。 

Java代碼  收藏代碼
  1. public class CompareTest {  
  2.   
  3.     public static void main(String[] args) {  
  4.         Student stu1 = new Student(1"Little");  
  5.         Student stu2 = new Student(2"Cyntin");  
  6.         Student stu3 = new Student(3"Tony");  
  7.         Student stu4 = new Student(4"Gemini");  
  8.           
  9.         Student[] stus = new Student[4];  
  10.         stus[0] = stu1;  
  11.         stus[1] = stu4;  
  12.         stus[2] = stu3;  
  13.         stus[3] = stu2;  
  14.         System.out.println(“Array: ” + Arrays.toString(stus));   
  15.         Arrays.sort(stus);   
  16.         System.out.println(“Sort:  ” + Arrays.toString(stus));  
  17.     }  
  18. }  

  Student 數組裏添加元素的順序並不是按學號 id 來添加的。調用了 Arrays.sort(stus) 之後,對 Student 數組進行排序,不管 sort 是使用哪種排序算法來實現的,比較兩個對象“大小”這個操作,它是肯定要做的。那麼如何比較兩個對象的“大小”? Student 實現的 Comparable 接口就發揮作用了。 sort 方法會將待比較的那個對象強制類型轉換成 Comparable ,並調用 compareTo 方法,根據其返回值來判斷這兩個對象的“大小”。所以,在這個例子中排序後的原 Student 亂序數組就變成了按學號排序的 Student 數組。 

  但是我們注意到,排序算法和 Student 類綁定了, Student 只有一種排序算法。但現實社會不是這樣的,如果我們不想按學號排序怎麼辦?假如,我們想按姓名來給學生排序怎麼辦?我們只能修改 Student 類的 Comparable 接口的 compareTo 方法,改成按姓名排序。如果在同一個系統裏有兩個操作,一個是按學號排序,另外一個是按姓名排序,這怎麼辦?不可能在 Student 類體中寫兩個 compareTo 方法的實現。這麼看來Comparable就有侷限性了。爲了彌補這個不足,JDK 還爲我們提供了另外一個排序方式,也就是下面要說的比較器排序。 

二、比較器排序:java.util.Comparator 
  上面我提到了,之所以提供比較器排序接口,是因爲有時需要對同一對象進行多種不同方式的排序,這點自然排序 Comparable 不能實現。另外, Comparator 接口的一個好處是將比較排序算法和具體的實體類分離了。 

  翻翻 API 會發現, Arrays.sort 還有種重載形式:sort(T[] a, Comparator<? super T> c) ,這個方法參數的寫法用到了泛型,我們還沒講到。我們可以把它理解成這樣的形式: sort(Object[] a, Comparator c) ,這個方法的意思是按照比較器 c 給出的比較排序算法,對 Object 數組進行排序。Comparator 接口中定義了兩個方法: compare(Object o1, Object o2) 和 equals 方法,由於 equals 方法所有對象都有的方法,因此當我們實現 Comparator 接口時,我們只需重寫 compare 方法,而不需重寫 equals 方法。Comparator 接口中對重寫 equals 方法的描述是:“注意,不重寫 Object.equals(Object) 方法總是安全的。然而,在某些情況下,重寫此方法可以允許程序確定兩個不同的 Comparator 是否強行實施了相同的排序,從而提高性能。”。我們只需知道第一句話就OK了,也就是說,可以不用去想應該怎麼實現 equals 方法,因爲即使我們不顯示實現 equals 方法,而是使用Object類的 equals 方法,代碼依然是安全的。而對於第二句話,究竟是怎麼提高比較器性能的,我也不瞭解,所以就不說了。 

  那麼我們來寫個代碼,來用一用比較器排序。還是用 Student 類來做,只是沒有實現 Comparable 接口。由於比較器的實現類只用顯示實現一個方法,因此,我們可以不用專門寫一個類來實現它,當我們需要用到比較器時,可以寫個匿名內部類來實現 Comparator 。下面是我們的按姓名排序的方法: 

Java代碼  收藏代碼
  1. public void sortByName () {  
  2.     Student stu1 = new Student(1"Little");  
  3.     Student stu2 = new Student(2"Cyntin");  
  4.     Student stu3 = new Student(3"Tony");  
  5.     Student stu4 = new Student(4"Gemini");  
  6.       
  7.     Student[] stus = new Student[4];  
  8.     stus[0] = stu1;  
  9.     stus[1] = stu4;  
  10.     stus[2] = stu3;  
  11.     stus[3] = stu2;  
  12.     System.out.println("Array: " + Arrays.toString(stus));  
  13.   
  14.     Arrays.sort(stus, new Comparator() {  
  15.   
  16.         @Override  
  17.         public int compare(Object o1, Object o2) {  
  18.             if (o1 instanceof Student && o2 instanceof Student) {  
  19.                 Student s1 = (Student) o1;  
  20.                 Student s2 = (Student) o2;  
  21.                 //return s1.getId() - s2.getId(); // 按Id排  
  22.                 return s1.getName().compareTo(s2.getName()); // 按姓名排  
  23.             }  
  24.             return 0;  
  25.         }  
  26.           
  27.     });  
  28.       
  29.     System.out.println("Sorted: " + Arrays.toString(stus));  
  30. }  

  當我們需要對Student按學號排序時,只需修改我們的排序方法中實現Comparator的內部類中的代碼,而不用修改 Student 類。 

  P.S. 當然,你也可以用 Student 類實現 Comparator 接口,這樣Student就是(is a)比較器了(Comparator)。當需要使用這種排序的時候,將 Student 看作 Comparator 來使用就可以了,可以將 Student 作爲參數傳入 sort 方法,因爲 Student is a Comparator 。但這樣的代碼不是個優秀的代碼,因爲我們之所以使用比較器(Comparator),其中有個重要的原因就是,這樣可以把比較算法和具體類分離,降低類之間的耦合。 

  上一篇博客裏說到了,TreeSet對這兩種比較方式都提供了支持,分別對應着TreeSet的兩個構造方法: 
    1、TreeSet():根據TreeSet中元素實現的 Comparable 接口的 compareTo 方法比較排序 
    2、TreeSet(Comparator comparator):根據給定的 comparator 比較器,對 TreeSet 中的元素比較排序 
  當向 TreeSet 中添加元素時,TreeSet 就會對元素進行排序。至於是用自然排序還是用比較器排序,就看你的 TreeSet 構造是怎麼寫的了。當然,添加第一個元素時不會進行任何比較, TreeSet 中都沒有元素,和誰比去啊? 

  下面,分別給出使用兩種排序比較方式的 TreeSet 測試代碼: 

Java代碼  收藏代碼
  1. /** 
  2.  * 使用自然排序 
  3.  * Student必須實現Comparable接口,否則會拋出ClassCastException 
  4.  */  
  5. public void testSortedSet3() {  
  6.     Student stu1 = new Student(1"Little");  
  7.     Student stu2 = new Student(2"Cyntin");  
  8.     Student stu3 = new Student(3"Tony");  
  9.     Student stu4 = new Student(4"Gemini");  
  10.   
  11.     SortedSet set = new TreeSet();  
  12.     set.add(stu1);  
  13.     set.add(stu3); // 若Student沒有實現Comparable接口,拋出ClassCastException  
  14.     set.add(stu4);  
  15.     set.add(stu2);  
  16.     set.add(stu4);  
  17.     set.add(new Student(12"Little"));  
  18.   
  19.     System.out.println(set);  
  20. }  


Java代碼  收藏代碼
  1. /** 
  2.  * 使用比較器排序 
  3.  * Student可以只是個簡單的Java類,不用實現Comparable接口 
  4.  */  
  5. public void testSortedSet3() {  
  6.     Student stu1 = new Student(1"Little");  
  7.     Student stu2 = new Student(2"Cyntin");  
  8.     Student stu3 = new Student(3"Tony");  
  9.     Student stu4 = new Student(4"Gemini");  
  10.   
  11.     SortedSet set = new TreeSet(new Comparator() {  
  12.   
  13.         @Override  
  14.         public int compare(Object o1, Object o2) {  
  15.             if (o1 instanceof Student  
  16.                     && o2 instanceof Student) {  
  17.                 Student s1 = (Student) o1;  
  18.                 Student s2 = (Student) o2;  
  19.                 return s1.getName().compareTo(s2.getName());  
  20.             }  
  21.             return 0;  
  22.         }  
  23.           
  24.     });  
  25.   
  26.     set.add(stu1);  
  27.     set.add(stu3);  
  28.     set.add(stu4);  
  29.     set.add(stu2);  
  30.     set.add(stu4);  
  31.     set.add(new Student(12"Little"));  
  32.   
  33.     System.out.println(set);  
  34. }  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章