Java 8 - мощное сравнение с лямбдами
1. обзор
В этом руководстве мы собираемся сначала взглянуть наLambda support in Java 8 – specifically at how to leverage it to write the Comparator and sort a Collection.
Эта статья является частьюthe “Java – Back to Basic” series здесь для примера.
Дальнейшее чтение:
Учебное пособие по API Java 8 Stream
Эта статья представляет собой примерное представление возможностей и операций, предлагаемых Java 8 Stream API.
Руководство по коллекционерам Java 8
В статье обсуждаются сборщики Java 8, показаны примеры встроенных сборщиков, а также показано, как создать пользовательский сборщик.
Лямбда-выражения и функциональные интерфейсы: советы и лучшие практики
Советы и рекомендации по использованию лямбда-кодов Java 8 и функциональных интерфейсов.
Сначала давайте определим простой класс сущности:
public class Human {
private String name;
private int age;
// standard constructors, getters/setters, equals and hashcode
}
2. Базовая сортировка без лямбда-выражений
До Java 8 при сортировке коллекции использовалисьcreating an anonymous inner class for the Comparator:
new Comparator() {
@Override
public int compare(Human h1, Human h2) {
return h1.getName().compareTo(h2.getName());
}
}
Это будет просто использоваться для сортировкиList сущностейHuman:
@Test
public void givenPreLambda_whenSortingEntitiesByName_thenCorrectlySorted() {
List humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);
Collections.sort(humans, new Comparator() {
@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. Базовая сортировка с поддержкой лямбда
С введением Lambdas мы теперь можем обойти анонимный внутренний класс и достичь того же результата сsimple, functional semantics:
(final Human h1, final Human h2) -> h1.getName().compareTo(h2.getName());
Аналогично - теперь мы можем проверить поведение так же, как и раньше:
@Test
public void whenSortingEntitiesByName_thenCorrectlySorted() {
List 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)));
}
Обратите внимание, что мы также используемthe new sort API added to java.util.List in Java 8 - вместо старого APICollections.sort.
4. Базовая сортировка без определений типов
Мы можем еще больше упростить выражение, не задавая определения типа -the compiler is capable of inferring these отдельно:
(h1, h2) -> h1.getName().compareTo(h2.getName())
И снова тест остается очень похожим:
@Test
public void
givenLambdaShortForm_whenSortingEntitiesByName_thenCorrectlySorted() {
List 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. Сортировка с использованием ссылки на статический метод
Затем мы собираемся выполнить сортировку с использованием лямбда-выражения со ссылкой на статический метод.
Сначала мы собираемся определить методcompareByNameThenAge - с той же сигнатурой, что и методcompare в объектеComparator<Human>:
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);
Конечным результатом является рабочая сортировка коллекции с использованием статического метода какComparator:
@Test
public void
givenMethodDefinition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {
List 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. Сортировка извлеченных компараторов
Мы также можем избежать определения даже самой логики сравнения, используя методinstance method reference иComparator.comparing, который извлекает и создаетComparable на основе этой функции.
Мы собираемся использовать геттерgetName() для построения лямбда-выражения и сортировки списка по имени:
@Test
public void
givenInstanceMethod_whenSortingEntitiesByName_thenCorrectlySorted() {
List 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 также представил вспомогательный метод дляreversing the comparator - мы можем быстро использовать его, чтобы отменить нашу сортировку:
@Test
public void whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
List humans = Lists.newArrayList(
new Human("Sarah", 10),
new Human("Jack", 12)
);
Comparator comparator
= (h1, h2) -> h1.getName().compareTo(h2.getName());
humans.sort(comparator.reversed());
Assert.assertThat(humans.get(0), equalTo(new Human("Sarah", 10)));
}
8. Сортировка по нескольким условиям
Лямбда-выражения сравнения не должны быть такими простыми - мы можем написатьmore complex expressions as well - например, сортируя объекты сначала по имени, а затем по возрасту:
@Test
public void whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {
List 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.
Starting with JDK 8, we can now chain together multiple comparators для построения более сложной логики сравнения:
@Test
public void
givenComposition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted() {
List 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()
Мы также можем отсортировать коллекцию с помощью API Java 8Streamsorted().
Мы можем отсортировать поток, используя естественный порядок, а также порядок, предоставляемыйComparator.. Для этого у нас есть два перегруженных варианта APIsorted():
-
sorted()–сортирует элементы aStream, используя естественный порядок; класс элемента должен реализовывать интерфейсComparable.
-
sorted(Comparator<?superT>comparator) - сортирует элементы на основе экземпляраComparator
Давайте посмотрим на пример того, какuse the sorted() method with natural ordering:
@Test
public final void
givenStreamNaturalOrdering_whenSortingEntitiesByName_thenCorrectlySorted() {
List letters = Lists.newArrayList("B", "A", "C");
List sortedLetters = letters.stream().sorted().collect(Collectors.toList());
assertThat(sortedLetters.get(0), equalTo("A"));
}
Теперь давайте посмотрим, как мы можемuse a custom Comparator with the sorted() API:
@Test
public final void
givenStreamCustomOrdering_whenSortingEntitiesByName_thenCorrectlySorted() {
List humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
Comparator nameComparator = (h1, h2) -> h1.getName().compareTo(h2.getName());
List sortedHumans =
humans.stream().sorted(nameComparator).collect(Collectors.toList());
assertThat(sortedHumans.get(0), equalTo(new Human("Jack", 12)));
}
Мы можем еще больше упростить приведенный выше пример, если мыuse the Comparator.comparing() method:
@Test
public final void
givenStreamComparatorOrdering_whenSortingEntitiesByName_thenCorrectlySorted() {
List humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
List 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() для сортировки коллекции в обратном порядке.
Во-первых, давайте посмотрим на пример того, какcombine the sorted()method with Comparator.reverseOrder() to sort a list in reverse natural order:
@Test
public final void
givenStreamNaturalOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
List letters = Lists.newArrayList("B", "A", "C");
List reverseSortedLetters = letters.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
assertThat(reverseSortedLetters.get(0), equalTo("C"));
}
А теперь давайте посмотрим, как мы можемuse the sorted() method and a custom Comparator:
@Test
public final void
givenStreamCustomOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
List humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
Comparator reverseNameComparator =
(h1, h2) -> h2.getName().compareTo(h1.getName());
List reverseSortedHumans = humans.stream().sorted(reverseNameComparator)
.collect(Collectors.toList());
assertThat(reverseSortedHumans.get(0), equalTo(new Human("Sarah", 10)));
}
Обратите внимание, что вызовcompareTo переворачивается, что и делает реверсирование.
Наконец, давайте упростим приведенный выше пример наusing the Comparator.comparing() method:
@Test
public final void
givenStreamComparatorOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() {
List humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
List reverseSortedHumans = humans.stream()
.sorted(Comparator.comparing(Human::getName, Comparator.reverseOrder()))
.collect(Collectors.toList());
assertThat(reverseSortedHumans.get(0), equalTo(new Human("Sarah", 10)));
}
12. Заключение
В этой статье проиллюстрированы различные захватывающие способы, которыми aList can be sorted using Java 8 Lambda Expressions - движется мимо синтаксического сахара к реальной и мощной функциональной семантике.
Реализация всех этих примеров и фрагментов кодаcan be found in the GitHub project - это проект на основе Eclipse, поэтому его должно быть легко импортировать и запускать как есть.