Java的Comparable與Comparator接口詳解


對集合或數組進行排序有兩種方法:

1.集合中的對象所屬的類實現了java.lang.Comparable 接口,然后調用Collections.sort()或者Arrays.sort()
2.實現java.lang.Comparator接口,把這個實現接口的類作為參數傳遞給上述的sort()方法。

先看Comparable<T>

java.lang 
Interface Comparable<T>

屬於Java集合框架下的一個接口。它只有一個方法 int compareTo(T o) 用於提供排序所需要的比較邏輯。

實現這個接口的類,其對象都可以通過調用Collections.sort()或者Arrays.sort()進行排序,根據compareTo的邏輯升序排列。

 1 /*
 2  * 方便起見省略getter&setting,主要演示接口作用。 
 3         這種實現並不嚴謹,因為沒有覆蓋 equals() 和 hashCode(),原因后面描述。       
 4         這個接口的作用:如果數組或者集合中的(類)元素實現了該接口的話 ,       
 5         可以調用 Collections.sort 和 Arrays.sort 排序,或應用於有序集合 TreeSet 和 TreeMap 中。 
 6  * 
 7  */
 8 public class Person implements Comparable<Person> {
 9     public int id;
10     public String name;
11     
12     public Person(int id,String name){
13         this.id=id;
14         this.name = name;
15     }
16 
17     public String toString(){
18         return "Person: "+id+" , "+name;
19     }
20     
21     /*
22      * 實現 Comparable 接口的抽象方法,定義排序規則
23      *  this < obj 返回負
24         this = obj 返回 0
25         this > obj 返回正
26      * @see java.lang.Comparable#compareTo(java.lang.Object)
27      */
28     @Override
29     public int compareTo(Person o) {
30         // TODO Auto-generated method stub
31         return this.id - o.id;
32     }    
33 }

 

測試類

 1 public class TestComparable {
 2 
 3     private static Person p1 = new Person(301,"a");
 4     private static Person p2 = new Person(100,"e");
 5     private static Person p3 = new Person(101,"d");
 6     private static Person p4 = new Person(143,"f");
 7     private static Person p5 = new Person(139,"b");
 8     private static Person p6 = new Person(113,"c");
 9     
10     public static void main(String[] args) {
11         // TODO Auto-generated method stub
12         List<Person> persons = new ArrayList<Person>();
13         persons.add(p1);
14         persons.add(p2);
15         persons.add(p3);
16         persons.add(p4);
17         persons.add(p5);
18         persons.add(p6);
19         
20         Collections.sort(persons);
21         System.out.println("--------------Result 1-----------------");
22         for(Person p : persons){
23             System.out.println(p.toString());
24         }
25         
26         TreeSet<Person> personSet = new TreeSet<Person>(); 
27         personSet.add(p1);
28         personSet.add(p2);
29         personSet.add(p3);
30         personSet.add(p4);
31         personSet.add(p5);
32         personSet.add(p6);
33         
34         System.out.println("---------------Result 2----------------");
35         for(Person p : personSet){
36             System.out.println(p.toString());
37         }
38         
39         Collections.sort(persons, new PersonComparator()); 
40         System.out.println("---------------Result 3----------------");
41         for(Person p :persons){
42             System.out.println(p.toString());
43         }
44         
45     }
46 
47 }

輸出:

--------------Result 1-----------------
Person: 100 , e
Person: 101 , d
Person: 113 , c
Person: 139 , b
Person: 143 , f
Person: 301 , a
---------------Result 2----------------
Person: 100 , e
Person: 101 , d
Person: 113 , c
Person: 139 , b
Person: 143 , f
Person: 301 , a
---------------Result 3----------------
Person: 301 , a
Person: 139 , b
Person: 113 , c
Person: 101 , d
Person: 100 , e
Person: 143 , f

Result 1是調用了Collections.sort(),而Result 2則是集合本身有序(Sorted Set/Sorted Map),例如TreeMap和TreeSet。使用非常簡單。

 

值得注意的有以下兩點:

1. 注意BigDecimal。所有實現 Comparable 的 Java 核心類都具有與 equals 一致的自然排序。java.math.BigDecimal 是個例外,它的自然排序將值相等但精確度不同的 BigDecimal 對象(比如 4.0 和 4.00)視為相等。

 1 public class Salary implements Comparable<Salary> {
 2     
 3     private BigDecimal money;
 4     
 5     public Salary(BigDecimal money){
 6         this.money = money;
 7     }
 8     
 9     public String toString(){
10         return this.money.toString();
11     }
12 
13     @Override
14     public int compareTo(Salary o) {
15 
16         return this.money.compareTo(o.money);
17         //Do NOT use: return (this.money.subtract(o.money)).intValue();
18     }
19 
20     
21     public static void main(String[] args) {
22         // TODO Auto-generated method stub
23         TreeSet<Salary> salarySet = new TreeSet<Salary>();
24         salarySet.add(new Salary(new BigDecimal(100.23)));
25         salarySet.add(new Salary(new BigDecimal(100.01)));
26         salarySet.add(new Salary(new BigDecimal(100.21009)));
27         salarySet.add(new Salary(new BigDecimal(100.2300)));
28         
29         for(Salary s : salarySet){
30             System.out.println(s.toString());
31         }
32     }
33 }

 

2. 保證類的自然順序與equals一致。在重寫 compareTo() 方法以定制比較邏輯時,需要確保其與等價性判斷方法 equals() 保持一致,即 e1.equals(e2)e1.compareTo(e2)==0 具有相同的值,這樣的話我們就稱自然順序就和 equals 一致。

在使用自然排序與 equals 不一致的元素(或鍵)時,沒有顯式比較器的有序集合(和有序映射表)行為表現“怪異”。特別是,這樣的有序集合(或有序映射表)違背了根據 equals 方法定義的集合(或映射表)的常規協定。

 1 public class PersonWithQQ implements Comparable<PersonWithQQ> {
 2     
 3     private int id;
 4     private int qq;
 5     
 6     public PersonWithQQ(int id,int qq){
 7         this.id = id;
 8         this.qq = qq;
 9     }
10     
11     //與compareTo的方法不一致,也就是與自然順序不一致。
12     @Override 
13     public boolean equals(Object o){          //......1
14         if(!(o instanceof PersonWithQQ)) 
15             return false;
16         PersonWithQQ person = (PersonWithQQ)o;
17         return this.id==person.id;
18     }
19     
20     //compareTo方法得到的順序,稱為自然順序。
21     @Override
22     public int compareTo(PersonWithQQ obj) {  //......2
23         // TODO Auto-generated method stub
24         return this.qq-obj.qq;
25     }
26 
27     public String toString(){
28         return id+","+qq;
29     }
30 
31     public static void main(String[] args) {
32         
33         TreeSet<PersonWithQQ> personSet = new TreeSet<PersonWithQQ>(); //......3
34         
35         personSet.add(new PersonWithQQ(101,301)); //...p1
36         personSet.add(new PersonWithQQ(101,302)); //...p2 
37         personSet.add(new PersonWithQQ(102,302)); //...p3
38         
39         for(PersonWithQQ p : personSet){
40             System.out.println(p.toString());
41         }
42         
43     }
44 }

可以看到,PersonWithQQ這個類equals比較的是id,而compareTo比較的是qq號碼,違反了e1.equals(e2) 和e1.compareTo(e2)==0 具有相同的布爾值。這樣,沒有顯式比較器的有序集合行為會“奇怪”。請看輸出:

101,301
101,302

 

事實上本來按照equals的方法,p1和p2應該是指同一個的對象,現在卻加入了不能允許有重復值的set集合里面。

對於p2和p3,我們有(!p2.equals(p3) && p2.compareTo(p3) == 0),p3無法加入到set集合里,set集合里只有兩個對象。這是因為在sorted set的角度,p2和p3是相等的,而卻跟equals的相等方法矛盾。這就是自然順序與equals不一致。

 

上述的Person類,其實也是不嚴謹的,因為沒有覆蓋equals方法來表達判斷對象相等的標准是id相等。

 

雖然是這種情況只發生在沒有顯式比較器有序集合中(sorted set/map),但是,在實現Comparable接口時,還是建議覆蓋equals方法和hashCode方法,保證與自然順序(CompareTo方法)一致,防止在有序集合中使用出問題。

 

解決方法很簡單,要不就重新覆蓋equals或compareTo的其中一個方法,使其與另一個方法一致。要不就顯式使用比較器。

上述PersonWithQQ類中第33行代碼替換為:

 1 //顯式使用Comparator
 2         TreeSet<PersonWithQQ> personSet = new TreeSet<PersonWithQQ>(new Comparator<PersonWithQQ>() {
 3 
 4             @Override
 5             public int compare(PersonWithQQ o1, PersonWithQQ o2) {
 6                 // TODO Auto-generated method stub
 7                 
 8                 return o1.id-o2.id;
 9             }
10             
11         });

本質上來說,使用比較器也是為了使自然順序與equals一致,這樣最為穩妥。

 

再來看Comparator<T>

java.util 
Interface Comparator<T>

簡單來說,Comparator是策略模式的一種實現,體現了將類和排序算法相分離的原則。

1 public class PersonComparator implements Comparator<Person> {
2 
3     @Override
4     public int compare(Person p0, Person p1) {
5         // TODO Auto-generated method stub
6         
7         return p0.name.compareTo(p1.name);
8     }
9 }

這是Person類的另一種排序方法,根據名字字母排序,因此輸出不再根據id排列:

---------------Result 3----------------
Person: 301 , a
Person: 139 , b
Person: 113 , c
Person: 101 , d
Person: 100 , e
Person: 143 , f

而這個Comparator可以作為參數傳遞給sort方法,提供各種不同的排序方法。

在很多時候,對於需要排序的原始類,例如Person類、PersonWithQQ類,不一定能修改代碼使其實現Comparable接口;這時候Comparator就可以發揮作用了。

 

 

參考:

http://docs.oracle.com/javase/6/docs/api/java/lang/Comparable.html

http://docs.oracle.com/javase/6/docs/api/java/util/Comparator.html

http://blog.csdn.net/itm_hadf/article/details/7432782


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM