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

原文地址:https://www.cnblogs.com/techyc/p/2679718.html