對集合或數組進行排序有兩種方法:
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