Comparator 比较器 reversed之谜

1,864 阅读2分钟

背景

日常开发中 ,要给一个 List,用两个属性进行降序处理返回。比较器默认排序方式是升序,

经常用 Comparator的 reversed() 方法生成降序比较器。两个属性都是降序,就想当然调用了两次 reversed() 方法,可排序后的结果却不是正确的。

查看了几篇文章,说是两个改为一个,在尾部加上 reversed()就可以了,觉得有点奇怪🤔,自己就决定一探 reversed()源码的奥秘。  

公司业务代码不便透露,写个简单的代码阐述下事件

StudentInfo 列表,想以 score降序排列,如果score相等的情况下,再以 age 进行降序排序

public class StudentInfo {
    private String name;
    private int age;
    private int score;
}


public static void main(String[] args) {
    StudentInfo a = new StudentInfo("cici", 17, 80);
    StudentInfo b = new StudentInfo("tony", 18, 80);
    StudentInfo c = new StudentInfo("mark", 19, 79);
    List<StudentInfo> list = new ArrayList<>();
    list.add(a);
    list.add(b);
    list.add(c);
    
    //想当然的reversed 使用
    list.sort(Comparator.comparing(StudentInfo::getScore).reversed().thenComparing(StudentInfo::getAge).reversed());
    for (int i = 0; i < list.size(); i++) {
        System.out.println(list.get(i).toString());
    }

   

运行结果如下:

image.png

  第二个属性 age 以降序进行排序,但是第一个属性 score 并没有实现降序的效果这是为什么呢?

源码分析

List.sort()

首先看 List.sort 的源码

image.png

底部用到的是 Arrays.sort()进行排序,则 List.sort()方法里的参数,是一个要自定义实现的 Comparator 比较器。

实现一个比较器实例,需要重写里面的一个抽象方法 compare()  

Comparator.comparing()

追踪 Comparator.comparing()构建比较器源码

image.png

Function 是一个 函数接口,包含一种 apply()方法,来实现方法调用。

参数 Function<? super T, ? extends U> keyExtractor 表示输入一个是 T 类型对象,输出一个 U 类型的对象,举个例子,输入一个 StudentInfo 对象返回其分数 score 数值:

Function<StudentInfo, Integer> getScore = StudentInfo::getScore;

compareTo() 方法,两个 Integer 类型 score 进行比较

可以看到 源码实现了一个匿名类比较器,用 两个 Integer 类型 score 进行compareTo() 方法比较, 来重写 Comparator 里的唯一抽象方法 compare()。

reversed()

image.png

借用了 集合类里的 reverseOrder()

image.png  

看代码, 生成了一个 ReverseComparator2 ,一个实现了Comparator接口的Collections的静态内部类。

image.png

降序的原理在这里,原来 ReverseComparator2 内部持有 原比较器 cmp,重写 compare() 方法,内部调用 cmp时,把 t1, t2 顺序对调,以此达到排序反转的效果。

突然有些理解了上上图 ,里的代码含义

if (cmp instanceof ReverseComparator2)

如果接连写上两个 reversed().reversed()方法,会直接把原比较器返回。

thenComparing()

image.png

又生成一个 Comparator

image.png

  一个匿名类,重写的 compare方法里,组合式的调用前两个comparator比较器,两个 comparator 比较器 执行 compare() 方法比较元素大小。有点递归那感觉了。

谜题解开

现在到了最后关键的时刻,最后一个尾部执行 reversed()方法,就相当于在新的比较器里,整个排序反转,可是第一个比较器已经反转过了,再次反转就会恢复升序的效果。那按代码分析,只要最后一个写上 reversed()即可,实验一下,果然如此

image.png

总结

个人感觉 reversed() 方法不仔细看源码,很容易使用出错,感觉类似于有累加效果。

为了不混淆出错,构建比较器时,推荐使用 

Comparator.comparing(StudentInfo::getScore, Collections.reverseOrder())

这个是直接创建一个比较器,如前方还有别的比较器,并无影响。

平时遇到问题,持续深究,也是很有意味的。