• 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

  • 相关阅读:
    Spring Data框架
    Flutter入门坑一Could not resolve com.android.tools.build:gradle:3.2.1.
    圆形图像
    MissingPluginException(No implementation found for method getDatabasesPath on channel com.tekartik.sqflite)
    flutter应用打包、修改图标、启动页和app名字
    sqflite常用操作
    Flutter解决神奇的ListView顶部多一段空白高度的问题
    Flutter-CircleAvatar圆形和圆角图片
    flutter 报错 DioError [DioErrorType.DEFAULT]: Bad state: Insecure HTTP is not allowed by platform
    【flutter 溢出BUG】 bottom overflowed by xxx PIXELS
  • 原文地址:https://www.cnblogs.com/techyc/p/2679718.html
Copyright © 2020-2023  润新知