Comparator reversed()踩坑

本文分享java代码下Comparator类reversed()排序的踩坑记录,而后探究相关源码,并做出总结。




假设有下面这样的用户类,此时你需要对它的列表进行排序,先按照id降序,再按照age降序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 用户类
*/
class User {
private long id;
private int age;

public User(long id, int age) {
this.id = id;
this.age = age;
}

public long getId() {
return id;
}

public int getAge() {
return age;
}

@Override
public String toString() {
return "User{id=" + id + ", age=" + age + "}";
}
}

那么你很可能写出下面这样的示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 示例代码
*/
public class Main {
public static void main(String[] args) {
List<User> users = Arrays.asList(
new User(1L, 30),
new User(2L, 25),
new User(3L, 35),
new User(3L, 30)
);
// 排序代码 ❌
Comparator<User> comparator = Comparator.comparingLong(User::getId).reversed()
.thenComparingInt(User::getAge).reversed();

// 排序,打印结果
List<User> streamSortResult = users.stream()
.sorted(comparator)
.collect(Collectors.toList());
streamSortResult.forEach(System.out::println);
}
}

即使你问AI,它也会让你这样写,但其实这种写法是错误的。

screenshot_2025-02-27_10-06-47.png

  实际运行上述代码,控制台打印的输出是下面这样的。很明显,实际输出结果是先按照id升序,再按照age降序

6E0EC4BC-DE63-476b-B61F-696EA44226F1.png




源码探究

reversed() 开始分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@FunctionalInterface
public interface Comparator<T> {

default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}

public static <T> Comparator<T> reverseOrder(Comparator<T> cmp) {
if (cmp == null)
return reverseOrder();

if (cmp instanceof ReverseComparator2)
return ((ReverseComparator2<T>)cmp).cmp;
// 🎯 <1> 使用ReverseComparator类型装饰
return new ReverseComparator2<>(cmp);
}
}

  可以看到位置<1>,reverseOrder()方法会将入参使用ReverseComparator类型装饰。此类是Collections的静态内部类,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class Collections {

private static class ReverseComparator2<T> implements Comparator<T>,
Serializable
{
private static final long serialVersionUID = 4374092139857L;

final Comparator<T> cmp;

ReverseComparator2(Comparator<T> cmp) {
assert cmp != null;
this.cmp = cmp;
}

// 🎯 <2>参数交换了顺序,也就是之前的排序规则都反转了
public int compare(T t1, T t2) {
return cmp.compare(t2, t1);
}

public boolean equals(Object o) {
return (o == this) ||
(o instanceof ReverseComparator2 &&
cmp.equals(((ReverseComparator2)o).cmp));
}

public int hashCode() {
return cmp.hashCode() ^ Integer.MIN_VALUE;
}

@Override
public Comparator<T> reversed() {
return cmp;
}
}
}

位置<2>中交换了参数顺序,那也就是反转了前面的排序规则




总结:

reversed()方法将会反转前面的排序规则。

1
2
3
4
5
6
// 先按照id降序,再按照age降序
Comparator.comparingLong(User::getId)
.thenComparingInt(User::getAge).reversed()
// 先按照id升序,再按照age降序
Comparator.comparingLong(User::getId).reversed()
.thenComparingInt(User::getAge).reversed()

吐槽:虽然Comparator类使用@FunctionalInterface声明,但里面很多default修饰的默认方法