Comparator 与 Lambda 结合
1. 天作之合
原因:Comparator
是一个函数式接口 (Functional Interface,如果一个接口只要有且仅有一个抽象方法,它就是函数式接口)。
Comparator
的核心方法正是 int compare(T o1, T o2)
。
Lambda 表达式的本质就是对函数式接口的匿名实现。因此,我们不再需要写一个完整的匿名内部类,只需要提供与compare
方法签名匹配的一小段代码即可。
2. 从传统到 Lambda 的演变
这里通过定义一个 Student 类作为例子。
public class Student {
private String name;
private int age;
// 构造函数、getter、toString 省略...
}
假设我们要按年龄降序排序。
-
传统方式:匿名内部类
List<Student> students = ...; Collections.sort(students, new Comparator<Student>() { @Override public int compare(Student s1, Student s2) { // 降序:s2 在前 return s2.getAge() - s1.getAge(); } });
这段代码很冗长,有很多模板化的代码 (new Comparator..., @Override...)
。
- Lambda 表达式方式 Lambda 表达式让我们只关注最重要的部分:比较逻辑。
// 使用 Lambda 表达式
students.sort((Student s1, Student s2) -> {
return s2.getAge() - s1.getAge();
});
这已经简洁很多了,同时 Java 编译器足够聪明,可以进一步简化:
- 可以省略参数类型
(Student s1, Student s2) -> (s1, s2)
- 如果方法体只有一行,可以省略大括号 {} 和 return
// 终极简化版 Lambda
students.sort((s1, s2) -> s2.getAge() - s1.getAge());
list.sort()
是 Java 8 中 List 接口新增的默认方法,它接受一个 Comparator
。
3. Comparator 的静态辅助方法:更优雅的 Lambda
Java 8 的 Comparator
接口还提供了一系列静态辅助方法,让代码更具可读性和表现力,这些方法会返回一个 Comparator
实例。
最常用的就是 Comparator.comparing()
,这里详细介绍一下这个方法:
- 单字段排序
Comparator.comparing()
接收一个函数,该函数从对象中提取用于排序的Key
。
// 按年龄升序排序:
// 传入一个提取 age 的 Lambda
students.sort(Comparator.comparing(student -> student.getAge()));
// 使用“方法引用”(Method Reference) 的方式,更加简洁!
students.sort(Comparator.comparing(Student::getAge));
//Student::getAge 的写法叫做方法引用,它等同于 student -> student.getAge()。当 Lambda 表达式只是在调用一个已存在的方法时,方法引用是首选,因为它最清晰。
//按姓名升序排序:
students.sort(Comparator.comparing(Student::getName));
- 降序排序
如果想要降序排序,只需要在
Comparator
后面链式调用.reversed()
方法即可。
//按年龄降序排序:
students.sort(Comparator.comparing(Student::getAge).reversed());
这种方式比 (s1, s2) -> s2.getAge() - s1.getAge()
更安全(因为避免了整数溢出问题),且更具可读性。
- 多字段组合排序 (链式比较)
这是
Comparator
辅助方法最强大的功能之一。如果需要先按一个字段排序,如果该字段值相同,再按另一个字段排序,可以使用.thenComparing()
。
//需求:先按年龄升序排序,如果年龄相同,再按姓名升序排序。
students.sort(
Comparator.comparing(Student::getAge)
.thenComparing(Student::getName)
);
这使得代码读起来就像在描述需求一样,非常直观
//更复杂的需求:先按年龄降序排序,如果年龄相同,再按姓名升序排序。
students.sort(
Comparator.comparing(Student::getAge).reversed() // 年龄降序
.thenComparing(Student::getName) // 姓名升序
);
- 处理 null 值
如果排序的字段可能是 null,直接比较会抛出
NullPointerException
。Comparator
提供了优雅的处理方式。
Comparator.nullsFirst()
: 将 null 值排在最前面。Comparator.nullsLast()
: 将 null 值排在最后面。
//需求:按姓名排序,但 null 姓名的学生排在最后。
// naturalOrder() 表示使用字段本身的自然顺序(这里是 String 的字典序)
students.sort(
Comparator.comparing(Student::getName,
Comparator.nullsLast(Comparator.naturalOrder()))
);
4. 总结
场景 | Lambda 推荐写法 |
---|---|
单字段升序 | list.sort(Comparator.comparing(ClassName::getFieldName)); |
单字段降序 | list.sort(Comparator.comparing(ClassName::getFieldName).reversed()); |
多字段组合排序 | list.sort(Comparator.comparing(C::getField1).thenComparing(C::getField2)); |
自定义复杂逻辑 | list.sort((o1, o2) -> { /* 比较逻辑 */ }); |
处理 null 值 | Comparator.comparing(C::getField, Comparator.nullsLast(Comparator.naturalOrder())) |
评论区
请登录后发表评论