Java中Comparable接口和Comparator比较器的使用方法

Comparable接口和Comparator比较器是实现将一个泛型为某个引用数据类型的集合容器中元素按一定顺序排序的两种方式,下面将对这两种方式进行讲解

一、首先定义一个学生类

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 int getAge() {
        return age;
    }
    /**
     * 由于Set集合需要去重,所以当泛型传入这个Student类型的时候,
     * Student需要重写equals()和hashCode()两个方法来保证两个元素逻辑上的不同
     * 逻辑上的不同就是说两个元素应该名字或者年龄这些属性不同就算作是不同对象,如果地址不同,但是名字年龄等所有属性都相同的话应该算作同一个对象)
     * 如果没有重写equals()和hashCode()方法的话,会造成去重失败
     */

    /**
     * equals()和hashCode()方法的重写可以借助IDEA的快捷键(alt+insert)来重写,get和set方法还有构造方法也可以
     * 我这里就是借助IDEA自动生成的,但是一定要知道重写的原则是什么
     * equals()方法重写原则:
     * 1、对称性:x,y非空,x.equals(y)和y.equals(x)返回值相同
     * 2、自反性:x非空,x.equals(x)应该返回true
     * 3、传递性:x.equals(y)为true,y.equals(z)也为true,那x.equals(z)应该也为true
     * 4、一致性:对于非空x,y,只要对象的相关信息没有被修改,x.equals(y)多次调用应该始终返回相同的结果
     *
     * @param o 比较另一个对象
     * @return true/false表示是否为同一个对象
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    /**
     * hashCode()方法的重写准则:
     * 1、如果两个对象通过equals()方法比较相等的话,那么它们的hashCode()方法应返回相同的值
     * 2、如果两个对象通过equals()方法比较不相等,那么它们的hashCode()方法可以返回相同或不同的值(尽量避免冲突,以提高哈希表性能)
     * 一般调用顶级父类Objects的hash()方法进行hash计算即可
     *
     * @return 该对象生成的hashCode的int值
     */
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

二、然后在main()方法中创建学生类对象,并添加到容器treeSet中,使用增强for循环遍历treeSet集合发现……

public static void main(String[] args) {
    Student s1 = new Student("zhao", 18);
    Student s2 = new Student("li", 20);
    Student s3 = new Student("zhang", 19);
    TreeSet<Student> treeSet = new TreeSet<>();
    treeSet.add(s1);
    treeSet.add(s2);
    treeSet.add(s3);
    for (Student s:treeSet){
        System.out.println(s.getName()+s.getAge());
    }
}

报错了

image.png

这个报错的原因是Student类没有实现Comparable接口

三、修改Student类实现Comparable接口如下

public class Student implements Comparable<Student> {
 /**
  *
  .......
  */

    /**
     * 实现Comparable接口需要重写compareTo()方法
     *
     * @param o the object to be compared.
     * @return ==0:表示比较的元素与原来的元素相同,所以由于Set集合的不可重复性,在main()方法中添加到treeSet集合中的元素只有第一个学生s1对象
     * >0:表示升序排列,或者是按照add的顺序添加
     * <0:表示降序排列
     */
    @Override
    public int compareTo(Student o) {
        //保证年龄按姓名的字母顺序排序
        int num = this.age - o.age;
        //年龄相同时,按照姓名的字母顺序排序
        int nums2 = num == 0 ? this.name.compareTo(o.name) : num;
        return nums2;
    }
}

四、这样就可以实现在集合中按照compareTo()方法中定义的排序方式排序

测试

public static void main(String[] args) {
    Student s1 = new Student("zhao", 18);
    Student s2 = new Student("li", 20);
    //重复元素不会添加
    Student s4 = new Student("li", 20);
    //虽然名字一样,但是年龄不一样,在equals()的校验中是不同对象所以会被添加到集合
    Student s5 = new Student("li", 21);
    Student s6 = new Student("lin", 19);
    Student s3 = new Student("zhang", 19);
    TreeSet<Student> treeSet = new TreeSet<>();
    treeSet.add(s1);
    treeSet.add(s2);
    treeSet.add(s3);
    treeSet.add(s4);
    treeSet.add(s5);
    treeSet.add(s6);
    for (Student s : treeSet) {
        System.out.println(s.getName() + " - " + s.getAge());
    }
}

测试结果:元素无重复,按照年龄升序,年龄相同时,按照姓名字母顺序排序实现成功

image.png

五、下面使用Comparator比较器来做同样的实现

Comparator比较器是在构造方法传入Comparator具体实现时候定义排序规则的

程序如下

public static void main(String[] args) {
    TreeSet<Student> treeSet = new TreeSet<>(new Comparator<Student>() {
        @Override
        public int compare(Student o1, Student o2) {
            int num = o1.getAge() - o2.getAge();
            int num2 = num == 0 ? o1.getName().compareTo(o2.getName()) : num;
            return num2;
        }
    });
    /**
     * 可以使用Lambda表达式简化如下
     * TreeSet<Student> treeSet = new TreeSet<>((o1, o2) -> {
     *             int num = o1.getAge() - o2.getAge();
     *             int num2 = num == 0 ? o1.getName().compareTo(o2.getName()) : num;
     *             return num2;
     *         });
     */
    Student s1 = new Student("zhao", 18);
    Student s2 = new Student("li", 20);
    //重复元素不会添加
    Student s4 = new Student("li", 20);
    //虽然名字一样,但是年龄不一样,在equals()的校验中是不同对象所以会被添加到集合
    Student s5 = new Student("li", 21);
    Student s6 = new Student("lin", 19);
    Student s3 = new Student("zhang", 19);
    treeSet.add(s1);
    treeSet.add(s2);
    treeSet.add(s3);
    treeSet.add(s4);
    treeSet.add(s5);
    treeSet.add(s6);

    for (Student s : treeSet) {
        System.out.println(s.getName() + " - " + s.getAge());
    }
}

测试结果:同Comparable接口实现的测试结果

image.png

最后:关于Comparable接口和Comparator比较器的选择上

个人觉得,如果说项目需要在多个地方创建这个对象并且都按这个排序方式去排序的话就是使用Comparable接口来统一排序规则比较好,其他不需要多个地方创建对象调用的话比如刷算法题那选择Comparator比较器来做排序规则显然实现会更简单。

大功告成!