Java比较器:Comparator接口与Comparable接口的compare(compareTo)方法返回值的正负与升序、降序的关系

目录

在Java中经常会涉及到多个对象的排序问题,那么就涉及到对象之间的比较。

Java实现对象排序的方式有两种:

自然排序:java.lang.Comparable

定制排序:ava.util.Comparator

1. 自然排序:java.lang.Comparable

  • Comparable 接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序。 Comparable 接口中只有一个抽象方法:int compareTo(Object o);

  • 实现 Comparable 的类必须实现 compareTo(Object o) 方法,两个对象即通过 compareTo(Object o) 方法的返回值来比较大小。

    • 如果当前对象 this 大于形参对象 o ,则返回正整数,
    • 如果当前对象 this 小于形参对象 o ,则返回负整数,
    • 如果当前对象 this 等于形参对象 o ,则返回零。
  • 实现 Comparable 接口的类的对象数组(和有序集合)可以通过 Arrays.sort(和 Collections.sort )进行自动排序。

  • Comparable的典型实现:(默认都是从小到大排序

    String、包装类等实现了Comparable接口,重写了compareTo(obj)方法

    String:按照字符串中字符的Unicode值进行比较
    数值类型对应的包装类以及BigInteger、BigDecimal:按照它们对应的数值大小进行比较
    Character:按照字符的Unicode值来进行比较
    Boolean:true 对应的包装类实例大于 false 对应的包装类实例
    Date、Time等:后面的日期时间比前面的日期时间大

    代码示例:

    String[] arr = new String[]{"AA","cc","ac","dd","aa","FF","ff"};
    Arrays.sort(arr);
    System.out.println(Arrays.toString(arr)); // [AA, FF, aa, ac, cc, dd, ff]
    
  • 对于自定义类来说,如果需要排序,我们可以让自定义类实现 Comparable 接口,重写 compareTo(Object o) 方法。在 compareTo(Object o) 方法中指明如何排序。

    定义学生类:

    public class Student {
        private String name;
        private int age;
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

    测试类:

    public class ComparableTest {
        public static void main(String[] args) {
            Student[] students = new Student[5];
            students[0] = new Student("rose", 16);
            students[1] = new Student("jack", 18);
            students[2] = new Student("mark", 16);
            students[3] = new Student("john", 16);
            students[4] = new Student("lily", 17);
    
            Arrays.sort(students);
    
            for (int i = 0; i < students.length; i++) {
                System.out.println(students[i]);
            }
        }
    }
    

    发现程序出现了类型转换异常:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ggMQjfNv-1598369086597)(img/Comparable.png)]

    原因:对于自定义类来说,如果需要排序,自定义类必须实现 Comparable 接口,重写 compareTo(Object o) 方法。在 compareTo(Object o) 方法中指明如何排序。

    修改Student类,实现 Comparable 接口,指定按学生年龄升序排序:

    public class Student implements Comparable {
        ...
        
        @Override
        public int compareTo(Object o) {
     		Student student = (Student) o;
            if (this.age < student.age) {
                return -1;
            } 
            if (this.age > student.age) {
                return 1;
            }
            return 0;
        }
    }
    

    运行结果:

    Student{name='mark', age=15}
    Student{name='rose', age=16}
    Student{name='john', age=16}
    Student{name='lily', age=17}
    Student{name='jack', age=18}

    如果要按学生年龄降序排序,则修改compareTo方法:当前对象的年龄小于参数对象时,返回正整数;当前对象大于参数对象时,返回负整数:

    @Override
    public int compareTo(Object o) {
        Student student = (Student) o;
        if (this.age < student.age) {
            return 1;
        } 
        if (this.age > student.age) {
            return -1;
        }
        return 0;
    }
    

2. 定制排序:java.util.Compartor

  • 当元素的类型没有实现 java.lang.Comparable 接口而又不方便修改代码,或者实现了java.lang.Comparable接口的排序规则不适合当前的操作,那么可以考虑使用 Comparator 接口的实现类来排序。

  • Comparator 接口中只有两个抽象方法 int compare(Object o1, Object o2);boolean equals(Object obj);Comparator 接口实现类默认继承了 Object 类的 equals 方法,即间接实现了 equals 方法,因此只需实现 int compare(Object o1, Object o2) 即可。

  • 可以将 Comparator 接口实现类传递给 sort 方法(如 Arrays.sort 或 Collections.sort),从而允许在排序顺序上实现精确控制。

  • 重写 int compare(Object o1, Object o2) 方法,比较o1和o2的大小:

    如果要按照升序排序,
    则 o1小于o2返回负整数,o1与o2相等返回0,01大于02返回正整数

    如果要按照降序排序
    则 o1小于o2返回正整数,o1与o2相等返回0,01大于02返回负整数

    代码演示:

    定义Student类,无需实现Comparable接口:

    public class Student {
        private String name;
        private int age;
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    

    测试类:向sort方法中传入待排序的数组对象和Comparator接口实现类,指定按学生年龄升序排序:

    public class ComparableTest {
        public static void main(String[] args) {
            Student[] students = new Student[5];
            students[0] = new Student("rose", 16);
            students[1] = new Student("jack", 18);
            students[2] = new Student("mark", 15);
            students[3] = new Student("john", 16);
            students[4] = new Student("lily", 17);
    
            Arrays.sort(students, new Comparator() {
                @Override
                public int compare(Object o1, Object o2) {
                    Student s1 = (Student) o1;
                    Student s2 = (Student) o2;
                    //下面的代码可简化为 return s1.getAge() - s2.getAge();
                    if (s1.getAge() < s2.getAge()) {
                        return -1;
                    }
                    if (s1.getAge() > s2.getAge()) {
                        return 1;
                    }
                    return 0;
                }
            });
    
            for (int i = 0; i < students.length; i++) {
                System.out.println(students[i]);
            }
        }
    
    }
    

    运行结果:

    Student{name='mark', age=15}
    Student{name='rose', age=16}
    Student{name='john', age=16}
    Student{name='lily', age=17}
    Student{name='jack', age=18}

    如果要按学生年龄降序排序,则修改compare方法:当前对象的年龄小于参数对象时,返回正整数;当前对象大于参数对象时,返回负整数:

    @Override
    public int compare(Object o1, Object o2) {
        Student s1 = (Student) o1;
        Student s2 = (Student) o2;
        //下面的代码可简化为 return s2.getAge() - s1.getAge();
        if (s1.getAge() < s2.getAge()) {
            return 1;
        }
        if (s1.getAge() > s2.getAge()) {
            return -1;
        }
        return 0;
    }
    

    按年龄降序排序,年龄相同时按姓名首字母升序排序:

    @Override
    public int compare(Object o1, Object o2) {
        Student s1 = (Student) o1;
        Student s2 = (Student) o2;
        //第一条排序规则:年龄降序
        int result = s2.getAge() - s1.getAge();
        //第二条排序规则:年龄相同时,姓名首字母升序
        if (result == 0) {
            result = s1.getName().charAt(0) - s2.getName().charAt(0);
        }
        return result;
    }
    

    运行结果:

    Student{name='jack', age=18}
    Student{name='lily', age=17}
    Student{name='john', age=16}
    Student{name='rose', age=16}
    Student{name='mark', age=15}

3. 比较方法的返回值正负与升序、降序的关系

从前面已经知道:如果要按照升序排序,则 o1小于o2返回负整数,o1与o2相等返回0,01大于02返回正整数

如果要按照降序排序,则 o1小于o2返回正整数,o1与o2相等返回0,01大于02返回负整数

那么,对元素升序、降序排序时调整元素位置与接口中返回值的正负有什么关系呢?

测试环境:JDK8

原数组元素顺序:

Student{name='rose', age=16}
Student{name='jack', age=18}
Student{name='mark', age=15}
Student{name='john', age=16}
Student{name='lily', age=17}
  • 在compare方法中,不管o1、o2对象的内容,统一返回正整数:
@Override
public int compare(Object o1, Object o2) {
    return 1;
}

运行结果发现,数组元素顺序未改变。

  • 在compare方法中,不管o1、o2对象的内容,统一放回负整数,运行结果发现,数组元素倒置。
  • 在compare方法中,不管o1、o2对象的内容,统一放回0,运行结果发现,数组元素顺序未改变。

在comparable接口中及集合中的运行结果一致。

从而得出结论:在调用 compare(Object o1, Object o2) 方法时,会把两个元素中索引较小的元素赋值给o2,索引较大的元素赋值给o1。Comparable接口中compareTo(Object o) 方法的调用者是两个元素中索引较大的元素,参数对象 o 则是索引较小的元素。(JDK版本不同,对应关系可能不同)。通过debug调试或查看源码也能得出这个结论。

比如,对于数组{5, 10, 20, 15},比较前两个元素调用 compare(Object o1, Object o2) 方法时,会把5赋值给o2,把10赋值给o1。按升序排序时,若o1 > o2,则返回正整数,不调整元素位置;若o1 == o2,则返回0,不调整元素位置;若o1 < o2,则返回负整数,调整元素位置。按降序排序时,若o1 > o2,则返回负整数,需要调整元素位置;若o1 == o2,则返回0,不调整元素位置;若o1 < o2,则返回正整数,不调整元素位置。因此,不管是升序还是降序,只有在返回负整数时,才会调整元素的位置。

综上,排序时是否需要调整元素位置是由对象o1、o2与数组中元素的对应关系 和 比较方法返回值的正负共同决定的。

4. Comparable接口和Comparator接口的区别

只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码。

用Comparator 的好处是不需要修改源代码, 而是在待比较对象的类的外部实现一个比较器, 当某个自定义的对象需要作比较的时候,把待比较对象和比较器一起传递过去就可以实现排序功能。

此外,像String类、包装类等JDK内置类实现了Comparable接口默认是升序排序,如果要降序排序或指定其他排序规则只能使用Comparator接口。

上一篇:java中的值传递机制


下一篇:g++ 编译