源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用

文章目录

前言

对集合中的元素排序,我们可以使用 Collections 工具类如:

	List<Integer> list1 = new ArrayList<>();
	list1.add(2);
	list1.add(1);
	list1.add(5);
	System.out.println(list1);
	Collections.sort(list1);
	System.out.println(list1);
	// 输出
	[2, 1, 5]
	[1, 2, 5]

为什么是升序排序?
怎么改为降序呢?
如果想要对一个学生的属性进行排序?如:

class Student {
    public String name;
    public int age;
    public double score;

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}
public class TestDemo {
    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();
        list.add(new Student("张三", 12, 70));
        list.add(new Student("李四", 18, 81));
        list.add(new Student("王五", 21, 93));
        
        System.out.println(list);
        // 显然这里是不行的
        Collections.sort(list);
        System.out.println(list);
    }
}

显然这里是不行的,谁知道这是按学生的哪个属性排序,那么怎么去自定义排序的方式呢?


1.这里为什么是升序排序?

先说结论:Integer 包装类 实现了Comparable 接口,重写了 compareTo 方法,而 compareTo 方法的返回值影响排序是升序还是降序

1.1 我们来看看 JDK1.8 的源码

Collections.sort 源码:
源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用
我们这里的 List 保存的是 Integer 类型,源码这里可知 Integer 一定实现了Comparable 接口
我们看看 Integer包装类 确实继承了Comparable 接口
源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用

1.2 Comparable 接口

再看看 Comparable 接口,接口中只有一个 compareTo 的抽象方法, 返回一个 int 类型的值源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用
Integer 包装类中重写 compareTo 方法,compareTo 方法中调用了 compare 方法源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用
compare 方法返回的 0 或者 -1 或者 1 取决于传入两数的大小,在 x < y返回 -1,x > y 返回1,x = y 返回 0;
源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用

1.3 Comparable 接口中的 compareTo 方法和排序有什么关系?

sort 传入null
源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用
这里圈起来的都为 null
源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用
源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用
点进这里
源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用
继续
源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用
终于破案了,我们的 list 一路传递到这里,在这里调用了 刚才的 compareTo 方法,这里就是排序的部分代码,compareTo 方法的返回值影响排序是升序还是降序如果前一个数大于后一个数,返回1,交换顺序,升序排序。
源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用

2.怎么改为降序呢?

由上边的分析我们可知只需修改 compareTo 的比较方式就行,但是这里的 compareTo 方法是 Integer 包装类中重写的,我们没办法修改。那让我们再看看另外一个 sort 的重载方法,发现还有第二个参数
源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用

2.1 Comparator 接口

Comparator 接口中有一个 compare的抽象方法,也是返回一个 int 类型
源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用

2.2 Comparator 接口中的 compare 方法和排序有什么关系?

这次传入的第二个参数不为 null,进入这里
源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用
源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用
最后同 compareTo 一样,在最后 compare 比较返回值影响排序的结果
源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用

2.3 实现 Comparator 接口,改为降序排序

不会写没关系,直接复制刚才 Integer 里的代码,修改 (x < y) ? -1 : ((x == y) ? 0 : 1); 修改为
(x > y) ? -1 : ((x == y) ? 0 : 1),相当于调换了 1 和 -1 的返回

class MyCompare implements Comparator<Integer> {
    @Override
    public int compare(Integer x, Integer y) {
        return (x > y) ? -1 : ((x == y) ? 0 : 1);
    }
}
public class TestDemo {
    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<>();
        list1.add(2);
        list1.add(1);
        list1.add(5);
        System.out.println(list1);
        MyCompare myCompare = new MyCompare();
        Collections.sort(list1, myCompare);
        System.out.println(list1);
	}
}
// 输出:
[2, 1, 5]
[5, 2, 1]

我们还可以将 compare 方法修改为这样,这是相同的意思,而且更简便。

class MyCompare implements Comparator<Integer> {
    @Override
    public int compare(Integer x, Integer y) {
        return y - x;
    }
}

2.4 使用匿名内部类和 lambda 表达式

以下几种方式都可以实现降序排序,为方便记忆,o1 作为第一个参数, o2 作为第二个参数,o2 - o1 降序(2 ,1),o1 - o2 升序(1,2)

	Collections.sort(list1, new Comparator<Integer>() {
	    @Override
	    public int compare(Integer o1, Integer o2) {
	        return o2 - o1;
	    }
	});
	// lambda 表达式
	Collections.sort(list1, (Integer o1, Integer o2) -> {return o2 - o1; });
	// 简写
	Collections.sort(list1, (o2, o1) -> o2 - o1);
	// Collections.sort 也是调用了list.sort,所以也可以直接这样写
	list1.sort((o2, o1) -> o2 - o1);

3.对一个学生的属性进行排序

3.1 Student 类实现 Comparable 接口

因为 Student 是我们写的类,那么就可以学这 Integer 包装类 去实现 Comparable 接口,先看看刚才 Integer 是怎么写的
源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用
this.value 作为第一个参数,compareTo 传入的参数作为第二个参数
源码详解 Comparable 和 Comparator 接口, compareTo 方法和 compare 方法的区别和使用
我们也可以理解为

    public int compareTo(Integer o) {
        return this.value- o.value;
    }

自己实现

class Student implements Comparable<Student>{
    public String name;
    public int age;
    public double score;

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        return this.age - o.age;
    }
}
public class TestDemo {
    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();
        list.add(new Student("张三", 12, 70));
        list.add(new Student("李四", 21, 81));
        list.add(new Student("王五", 18, 93));
        System.out.println(list);
        Collections.sort(list);
        System.out.println(list);
    }
}
// 运行结果,按年龄升序排序
[Student{name='张三', age=12, score=70.0}, Student{name='李四', age=21, score=81.0}, Student{name='王五', age=18, score=93.0}]
[Student{name='张三', age=12, score=70.0}, Student{name='王五', age=18, score=93.0}, Student{name='李四', age=21, score=81.0}]

这里我们看出来要修改比较的东西时,就可能要重新修改重写的方法,而正常情况下Student 是封装好的,所以这个局限性就比较大,这才有了 Comparator 接口,也称之为比较器
而刚才在创建类时 就实现 Comparable 接口 定义的排序方式称自然排序或内部排序

3.2 基于 Comparator 接口,自定义比较器

public class TestDemo {
    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();
        list.add(new Student("张三", 12, 70));
        list.add(new Student("李四", 21, 81));
**加粗样式**        list.add(new Student("王五", 18, 93));
        System.out.println(list);
        // 自然排序
        Collections.sort(list);
        System.out.println(list);
        // 自定义比较器
        Collections.sort(list, (o1, o2) -> (int) (o2.score - o1.score));
        System.out.println(list);
    }
}

4.总结

Java 集合框架 Collections 工具类中的 sort 方法可以对集合中元素排序,前提是对象是可比较的,我们可以通过创建对象时实现Comparable 接口,或者在调用 sort 方法时将自己实现的 Comparator(比较器)接口作为参数传入。

4.1 Comparable 接口 和 Comparator 接口比较

  1. Comparable 接口在创建类时要手动实现并重写 compareTo 方法,一旦实现,每次用该类都有指定的顺序,属于内部顺序。如果要更换比较的方式,则要修改 compareTo 方法,侵入性强。

  2. Comparator(比较器)接口实现后作为参数传入Collections.sort。每次使用都要确定比较器,比较方式在实现比较器的时候重写 compare 方法确定,侵入性较弱。

上一篇:第一


下一篇:浅析 Comparable 和 Comparator