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

你可能感兴趣的:(comparator)