使用Lambdas表达式进行强力比较
翻译来源: Eugen Paraschiv 的 Java 8 – Powerful Comparison with Lambdas
1.概述
在本教程中,我们将首先了解Java 8中的Lambda支持 - 特别是如何利用它来编写Comparator并对Collection进行排序。
本文是Baeldung上“Java - Back to Basic”系列的一部分。
首先,让我们定义一个简单的实体类:
public class Human {
private String name;
private int age;
// standard constructors, getters/setters, equals and 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支持的基本排序
随着Lambdas的引入,我们现在可以绕过匿名内部类,并通过简单,功能的语义实现相同的结果:
(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中的java.util.List的新排序API - 而不是旧的Collections.sort API。
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 Expression执行排序,并引用静态方法。
首先,我们将定义方法compareByNameThenAge - 与Comparator 对象中的compare方法完全相同的签名:
public static int compareByNameThenAge(Human lhs, Human rhs) {
if (lhs.name.equals(rhs.name)) {
return lhs.age - rhs.age;
} else {
return lhs.name.compareTo(rhs.name);
}
}
现在,我们将使用此引用调用humans.sort方法:
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方法来避免定义甚至比较逻辑本身 - 该方法基于该函数提取和创建Comparable。
我们将使用getter getName()来构建Lambda表达式并按名称对列表进行排序:
@Test
public void
givenInstanceMethod_whenSortingEntitiesByNameThenAge_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 lhs.getAge() - rhs.getAge();
} else {
return lhs.getName().compareTo(rhs.getName());
}
});
Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}
9.按多种条件排序 - 组合
相同的比较逻辑 - 首先按名称排序,然后按年龄排序 - 也可以通过Comparator的新组合支持来实现。
从JDK 8开始,我们现在可以将多个比较器链接在一起,以构建更复杂的比较逻辑:
@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 sorted()API对集合进行排序。
我们可以使用自然排序以及比较器提供的排序对流进行排序。 为此,我们有sorted()API的两个重载变体:
- sorted() - 使用自然排序对Stream的元素进行排序; 元素类必须实现Comparable接口。
- sorted(Comparator <?super T> comparator) - 根据Comparator实例对元素进行排序
让我们看一个如何使用自然排序的sorted()方法的示例:
@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"));
}
现在让我们看看我们如何使用自定义Comparator与sorted() API:
@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()反向排序列表
我们也可以使用Stream.sorted()来反向排序集合。
首先,让我们看一个如何将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"));
}
现在,让我们看看如何使用sorted()方法和自定义Comparator:
@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)));
}
请注意,compareTo的调用被翻转,这就是正在进行的反转。
最后,让我们使用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.总结
这篇文章说明了使用Java 8 Lambda表达式对List进行排序的各种令人兴奋的方法 - 正好超越了语法糖并转化为真正强大的功能语义。