1. 概述
本文将深入探讨 Java 8 中 Lambda 表达式的核心应用场景——如何利用 Lambda 简化 Comparator 的编写,实现集合排序。
作为「Java 基础回顾」系列的一部分,我们先定义一个简单的实体类:
public class Human {
private String name;
private int age;
// 标准构造器、getter/setter、equals 和 hashcode 方法
}
2. 传统排序方式(无 Lambda)
在 Java 8 之前,集合排序需要创建匿名内部类实现 Comparator:
new Comparator<Human>() {
@Override
public int compare(Human h1, Human h2) {
return h1.getName().compareTo(h2.getName());
}
}
典型用法如下:
@Test
public void givenPreLambda_whenSortingEntitiesByName_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);
Collections.sort(humans, new Comparator<Human>() {
@Override
public int compare(Human h1, Human h2) {
return h1.getName().compareTo(h2.getName());
}
});
Assert.assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}
3. Lambda 支持的基本排序
引入 Lambda 后,我们可以用简洁的函数式语法替代匿名内部类:
(final Human h1, final Human h2) -> h1.getName().compareTo(h2.getName());
测试用例保持一致:
@Test
public void whenSortingEntitiesByName_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);
humans.sort(
(Human h1, Human h2) -> h1.getName().compareTo(h2.getName()));
assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}
⚠️ 注意:这里使用了 Java 8 新增的 List.sort()
API,而非传统的 Collections.sort()
。
4. 省略类型定义的简化排序
通过类型推断,我们可以进一步简化表达式:
(h1, h2) -> h1.getName().compareTo(h2.getName())
测试代码几乎不变:
@Test
public void
givenLambdaShortForm_whenSortingEntitiesByName_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);
humans.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));
assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}
5. 静态方法引用排序
接下来演示使用 Lambda 表达式引用静态方法进行排序。
首先定义与 Comparator<Human>
的 compare
方法签名一致的静态方法:
public static int compareByNameThenAge(Human lhs, Human rhs) {
if (lhs.name.equals(rhs.name)) {
return Integer.compare(lhs.age, rhs.age);
} else {
return lhs.name.compareTo(rhs.name);
}
}
然后通过方法引用调用:
humans.sort(Human::compareByNameThenAge);
完整测试:
@Test
public void
givenMethodDefinition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);
humans.sort(Human::compareByNameThenAge);
Assert.assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}
6. 提取比较器排序
通过实例方法引用和 Comparator.comparing
,我们可以避免显式编写比较逻辑:
@Test
public void
givenInstanceMethod_whenSortingEntitiesByName_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);
Collections.sort(
humans, Comparator.comparing(Human::getName));
assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}
7. 反向排序
JDK 8 提供了反转比较器的便捷方法:
@Test
public void whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);
Comparator<Human> comparator
= (h1, h2) -> h1.getName().compareTo(h2.getName());
humans.sort(comparator.reversed());
Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}
8. 多条件排序
Lambda 表达式支持构建复杂比较逻辑,例如先按姓名排序,再按年龄排序:
@Test
public void whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(
new Human("Sarah", 12),
new Human("Sarah", 10),
new Human("Zack", 12)
);
humans.sort((lhs, rhs) -> {
if (lhs.getName().equals(rhs.getName())) {
return Integer.compare(lhs.getAge(), rhs.getAge());
} else {
return lhs.getName().compareTo(rhs.getName());
}
});
Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}
9. 多条件组合排序
通过 Comparator 的链式调用,可以更优雅地实现多条件排序:
@Test
public void
givenComposition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(
new Human("Sarah", 12),
new Human("Sarah", 10),
new Human("Zack", 12)
);
humans.sort(
Comparator.comparing(Human::getName).thenComparing(Human::getAge)
);
Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}
10. 使用 Stream.sorted() 排序
Java 8 Stream API 的 sorted()
方法提供了另一种排序方式:
sorted()
– 使用自然排序(元素需实现Comparable
)sorted(Comparator<? super T> comparator)
– 使用自定义比较器
自然排序示例:
@Test
public final void
givenStreamNaturalOrdering_whenSortingEntitiesByName_thenCorrectlySorted() {
List<String> letters = Lists.newArrayList("B", "A", "C");
List<String> sortedLetters = letters.stream().sorted().collect(Collectors.toList());
assertThat(sortedLetters.get(0), equalTo("A"));
}
自定义比较器示例:
@Test
public final void
givenStreamCustomOrdering_whenSortingEntitiesByName_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
Comparator<Human> nameComparator = (h1, h2) -> h1.getName().compareTo(h2.getName());
List<Human> sortedHumans =
humans.stream().sorted(nameComparator).collect(Collectors.toList());
assertThat(sortedHumans.get(0), equalTo(new Human("Jack", 12)));
}
简化版(使用 Comparator.comparing()
):
@Test
public final void
givenStreamComparatorOrdering_whenSortingEntitiesByName_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
List<Human> sortedHumans = humans.stream()
.sorted(Comparator.comparing(Human::getName))
.collect(Collectors.toList());
assertThat(sortedHumans.get(0), equalTo(new Human("Jack", 12)));
}
11. 使用 Stream.sorted() 反向排序
结合 Comparator.reverseOrder()
实现自然序反向排序:
@Test
public final void
givenStreamNaturalOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
List<String> letters = Lists.newArrayList("B", "A", "C");
List<String> reverseSortedLetters = letters.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
assertThat(reverseSortedLetters.get(0), equalTo("C"));
}
自定义比较器反向排序:
@Test
public final void
givenStreamCustomOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
Comparator<Human> reverseNameComparator =
(h1, h2) -> h2.getName().compareTo(h1.getName());
List<Human> reverseSortedHumans = humans.stream().sorted(reverseNameComparator)
.collect(Collectors.toList());
assertThat(reverseSortedHumans.get(0), equalTo(new Human("Sarah", 10)));
}
简化版(使用 Comparator.comparing()
):
@Test
public final void
givenStreamComparatorOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
List<Human> reverseSortedHumans = humans.stream()
.sorted(Comparator.comparing(Human::getName, Comparator.reverseOrder()))
.collect(Collectors.toList());
assertThat(reverseSortedHumans.get(0), equalTo(new Human("Sarah", 10)));
}
12. 处理 null 值
传统实现遇到 null 会抛出 NullPointerException
:
@Test(expected = NullPointerException.class)
public void givenANullElement_whenSortingEntitiesByName_thenThrowsNPE() {
List<Human> humans = Lists.newArrayList(null, new Human("Jack", 12));
humans.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));
}
手动处理 null 值
将 null 元素移至列表末尾:
@Test
public void givenANullElement_whenSortingEntitiesByNameManually_thenMovesTheNullToLast() {
List<Human> humans = Lists.newArrayList(null, new Human("Jack", 12), null);
humans.sort((h1, h2) -> {
if (h1 == null) {
return h2 == null ? 0 : 1;
}
else if (h2 == null) {
return -1;
}
return h1.getName().compareTo(h2.getName());
});
Assert.assertNotNull(humans.get(0));
Assert.assertNull(humans.get(1));
Assert.assertNull(humans.get(2));
}
使用 nullsLast()
装饰器
更优雅的方案是使用 Comparator.nullsLast()
:
@Test
public void givenANullElement_whenSortingEntitiesByName_thenMovesTheNullToLast() {
List<Human> humans = Lists.newArrayList(null, new Human("Jack", 12), null);
humans.sort(Comparator.nullsLast(Comparator.comparing(Human::getName)));
Assert.assertNotNull(humans.get(0));
Assert.assertNull(humans.get(1));
Assert.assertNull(humans.get(2));
}
使用 nullsFirst()
装饰器
将 null 元素移至列表开头:
@Test
public void givenANullElement_whenSortingEntitiesByName_thenMovesTheNullToStart() {
List<Human> humans = Lists.newArrayList(null, new Human("Jack", 12), null);
humans.sort(Comparator.nullsFirst(Comparator.comparing(Human::getName)));
Assert.assertNull(humans.get(0));
Assert.assertNull(humans.get(1));
Assert.assertNotNull(humans.get(2));
}
✅ **强烈推荐使用 nullsFirst()
或 nullsLast()
**:代码更简洁,可读性更强,且更灵活。
13. 总结
本文系统展示了 Java 8 Lambda 表达式在集合排序中的多种应用方式,从基础语法到高级组合模式,充分体现了函数式编程的威力:
- ✅ 简化传统匿名内部类
- ✅ 支持方法引用和类型推断
- ✅ 实现多条件链式排序
- ✅ 结合 Stream API 灵活处理
- ✅ 优雅处理 null 值场景
所有示例代码可在 GitHub 仓库 获取。掌握这些技巧,能让你的排序代码既简洁又高效。