Lambda中比较器Comparator的使用

如果你赶时间:

1
2
list.sort((Developer o1, Developer o2) -> Integer.compare(o1.getAge(), o2.getAge()));   // 显式类型
listDevs.sort((o1, o2) -> Integer.compare(o1.getAge(), o2.getAge())); // 隐式类型,效果一致

一、概念

先来看下典型的比较器示例:

1
2
3
4
5
6
Comparator<Developer> byName = new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getName().compareTo(o2.getName()); // name是String类型
}
}

Java8中引入了Lambda表达式,比较器可以改成下面这样:

1
Comparator<Developer> byName = (Developer o1, Developer o2) -> o1.getName().compareTo(o2.getName());

对于数组Arrays.sort()提供了传入比较器的重载方法,对于列表Collections.sort()也提供了传入比较器的重载方法。

二、使用实例

2.1 通过new 新建比较器

假如我们要通过Developer 对象的年龄进行排序,通常情况下我们使用Collections.sort,new个匿名Comparator 类,类似下面这种:

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
36
37
38
39
40
41
42
43
44
45
46
47
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class TestSorting {

public static void main(String[] args) {

List<Developer> listDevs = getDevelopers();

System.out.println("Before Sort");
for (Developer developer : listDevs) {
System.out.println(developer);
}

//sort by age
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
// return o1.getAge() - o2.getAge();
return Integer.compare(o1.getAge(), o2.getAge());
}
});

System.out.println("After Sort");
for (Developer developer : listDevs) {
System.out.println(developer);
}

}

private static List<Developer> getDevelopers() {

List<Developer> result = new ArrayList<Developer>();

result.add(new Developer("ricky", new BigDecimal("70000"), 33));
result.add(new Developer("alvin", new BigDecimal("80000"), 20));
result.add(new Developer("jason", new BigDecimal("100000"), 10));
result.add(new Developer("iris", new BigDecimal("170000"), 55));

return result;

}

}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
Before Sort
Developer [name=ricky, salary=70000, age=33]
Developer [name=alvin, salary=80000, age=20]
Developer [name=jason, salary=100000, age=10]
Developer [name=iris, salary=170000, age=55]

After Sort
Developer [name=jason, salary=100000, age=10]
Developer [name=alvin, salary=80000, age=20]
Developer [name=ricky, salary=70000, age=33]
Developer [name=iris, salary=170000, age=55]

当比较规则发生变化时,你需要再次new个匿名Comparator 类:

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
//sort by age
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
// Tony updated: 尽管几乎不可能溢出,但存在这种可能性
// return o1.getAge() - o2.getAge();
// 参考这篇博客:https://xqtony.github.io/2023/05/26/compareTo/
// 应该使用下面的实现方法:
return Integer.compare(o1.getAget(), o2.getAge());
}
});

//sort by name
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getName().compareTo(o2.getName());
}
});

//sort by salary
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getSalary().compareTo(o2.getSalary());
}
});

这样也可以,不过你会不会觉得这样有点怪,因为其实不同的只有一行代码而已,但是却需要重复写很多代码?

2.2 通过Lambda表达式新建比较器

java8中,List接口直接提供了排序方法, 所以你不需要使用Collections.sort:

1
2
3
4
5
6
7
8
9
10
11
12
//List.sort() since Java 8
listDevs.sort(new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
// Tony:原文中存在两个问题:
// 1. 溢出风险
// 2. 顺序写反了
// return o2.getAge() - o1.getAge();
// Tony:
return Integer.compare(o1.getAget(), o2.getAge());
}
});

Lambda改写:

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
36
37
38
39
40
41
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

public class TestSorting {

public static void main(String[] args) {

List<Developer> listDevs = getDevelopers();

System.out.println("Before Sort");
for (Developer developer : listDevs) {
System.out.println(developer);
}

System.out.println("After Sort");

// Tony: 原文使用了下面的实现
// listDevs.sort((Developer o1, Developer o2)->o1.getAge()-o2.getAge());
listDevs.sort((Developer o1, Developer o2) -> Integer.compare(o1.getAge(), o2.getAge()));

//java 8 lambda feature
listDevs.forEach((developer)->System.out.println(developer));
// Tony注:
// listDevs.forEach(System.out::println);
}

private static List<Developer> getDevelopers() {

List<Developer> result = new ArrayList<Developer>();

result.add(new Developer("ricky", new BigDecimal("70000"), 33));
result.add(new Developer("alvin", new BigDecimal("80000"), 20));
result.add(new Developer("jason", new BigDecimal("100000"), 10));
result.add(new Developer("iris", new BigDecimal("170000"), 55));

return result;

}

}

输出:

1
2
3
4
5
6
7
8
9
10
11
Before Sort
Developer [name=ricky, salary=70000, age=33]
Developer [name=alvin, salary=80000, age=20]
Developer [name=jason, salary=100000, age=10]
Developer [name=iris, salary=170000, age=55]

After Sort
Developer [name=jason, salary=100000, age=10]
Developer [name=alvin, salary=80000, age=20]
Developer [name=ricky, salary=70000, age=33]
Developer [name=iris, salary=170000, age=55]

2.3 更多的例子

根据年龄:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 //sort by age
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
// return o1.getAge() - o2.getAge();
return Integer.compare(o1.getAget(), o2.getAge());
}
});

//lambda
// Tony 注:同样的问题
// listDevs.sort((Developer o1, Developer o2)->o1.getAge()-o2.getAge());
listDevs.sort((Developer o1, Developer o2) -> Integer.compare(o1.getAge(), o2.getAge()));

//lambda, valid, parameter type is optional
// Tony 注:同样的问题
// listDevs.sort((o1, o2)->o1.getAge()-o2.getAge());
listDevs.sort((o1, o2) -> Integer.compare(o1.getAge(), o2.getAge()));

根据名字:

1
2
3
4
5
6
7
8
9
10
11
12
13
//sort by name
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getName().compareTo(o2.getName());
}
});

//lambda
listDevs.sort((Developer o1, Developer o2)->o1.getName().compareTo(o2.getName()));

//lambda
listDevs.sort((o1, o2)->o1.getName().compareTo(o2.getName()));

根据薪水:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//sort by salary
Collections.sort(listDevs, new Comparator<Developer>() {
@Override
public int compare(Developer o1, Developer o2) {
return o1.getSalary().compareTo(o2.getSalary());
}
});

//lambda
// Tony注:使用compareTo()同样可以,本质上底层还是在调用compare()方法
listDevs.sort((Developer o1, Developer o2)->o1.getSalary().compareTo(o2.getSalary()));

//lambda
listDevs.sort((o1, o2)->o1.getSalary().compareTo(o2.getSalary()))

从小到大排序:

1
2
Comparator<Developer> salaryComparator = (o1, o2)->o1.getSalary().compareTo(o2.getSalary());
listDevs.sort(salaryComparator);

从大到小倒序:

1
2
3
4
5
6
Comparator<Developer> salaryComparator = (o1, o2)->o1.getSalary().compareTo(o2.getSalary());
listDevs.sort(salaryComparator.reversed());

// Tony 注:掘金版本给出了下面的实现:
// Comparator<Developer> salaryComparator = (o1, o2)->o2.getSalary().compareTo(o1.getSalary());
// 但是实际上根据Comparator接口中compare函数的定义,当o1的值小于o2,应该返回负值。因此上面这行的实现违反设计原则的,并不推荐。

本文虽然大部分是转载,但关于Integer类型的数值比较防止溢出问题,以及对compare()方法的设计均有重要注解,因此归类为二创。

参考:
java8-Lambda中比较器Comparator的使用 - CSDN
java8-Lambda中比较器Comparator的使用 - 掘金
Java8 Lambda表达式(二)System.out::println与Lambda表达式