对集合或数组进行排序有两种方法:
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