1. Обзор
В этом уроке мы сначала рассмотрим поддержку Lambda в Java 8 – в частности, как использовать ее для написания Компаратора и сортировки коллекции .
Эта статья является частью серии “Java – Back to Basic” здесь, на Baeldung.
Дальнейшее чтение:
Учебник по потоковому API Java 8
Руководство по сборщикам Java 8
Лямбда-выражения и функциональные интерфейсы: Советы и рекомендации
Во-первых, давайте определим простой класс сущностей:
public class Human { private String name; private int age; // standard constructors, getters/setters, equals and hashcode }
2. Базовая Сортировка Без Лямбд
До Java 8 сортировка коллекции включала бы создание анонимного внутреннего класса для Компаратора , используемого при сортировке:
new Comparator() { @Override public int compare(Human h1, Human h2) { return h1.getName().compareTo(h2.getName()); } }
Это будет просто использоваться для сортировки Списка сущностей Человека :
@Test public void givenPreLambda_whenSortingEntitiesByName_thenCorrectlySorted() { Listhumans = 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. Базовая Сортировка С Поддержкой Лямбды
С введением лямбд мы теперь можем обойти анонимный внутренний класс и достичь того же результата с помощью простой функциональной семантики :
(final Human h1, final Human h2) -> h1.getName().compareTo(h2.getName());
Аналогично – теперь мы можем проверить поведение так же, как и раньше:
@Test public void whenSortingEntitiesByName_thenCorrectlySorted() { Listhumans = 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))); }
Обратите внимание, что мы также используем новый sort API, добавленный в java.util.Список в Java 8 – вместо старых коллекций.сортировка API.
4. Базовая Сортировка Без Определений Типов
Мы можем еще больше упростить выражение, не указывая определения типов – компилятор способен вывести их самостоятельно:
(h1, h2) -> h1.getName().compareTo(h2.getName())
И опять же, тест остается очень похожим:
@Test public void givenLambdaShortForm_whenSortingEntitiesByName_thenCorrectlySorted() { Listhumans = 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 :
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 с этой ссылкой:
humans.sort(Human::compareByNameThenAge);
Конечным результатом является рабочая сортировка коллекции с использованием статического метода в качестве Компаратора :
@Test public void givenMethodDefinition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted() { Listhumans = 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() для построения лямбда-выражения и сортировки списка по имени:
@Test public void givenInstanceMethod_whenSortingEntitiesByName_thenCorrectlySorted() { Listhumans = 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() { Listhumans = 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. Сортировка С Несколькими Условиями
Сравнение лямбда – выражений не обязательно должно быть таким простым – мы можем написать более сложные выражения, а также – например, сортировка сущностей сначала по имени, а затем по возрасту:
@Test public void whenSortingEntitiesByNameThenAge_thenCorrectlySorted() { Listhumans = 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 .
Начиная с JDK 8, теперь мы можем объединить несколько компараторов для построения более сложной логики сравнения:
@Test public void givenComposition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted() { Listhumans = 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 Течение отсортированный() ИНТЕРФЕЙС ПРИКЛАДНОГО ПРОГРАММИРОВАНИЯ.
Мы можем сортировать поток, используя естественный порядок, а также порядок, предоставляемый компаратором . Для этого у нас есть два перегруженных варианта sorted() API:
- сортировка ed() – сортирует элементы потока с использованием естественного упорядочения; класс элементов должен реализовать интерфейс Comparable .
- сортировка(Comparator super T> compa rator) – сортировка элементов на основе экземпляра Comparator
Давайте рассмотрим пример того, как использовать метод sorted() с естественным упорядочением :
@Test public final void givenStreamNaturalOrdering_whenSortingEntitiesByName_thenCorrectlySorted() { Listletters = Lists.newArrayList("B", "A", "C"); List sortedLetters = letters.stream().sorted().collect(Collectors.toList()); assertThat(sortedLetters.get(0), equalTo("A")); }
Теперь давайте посмотрим, как мы можем использовать пользовательский компаратор с sorted() API :
@Test public final void givenStreamCustomOrdering_whenSortingEntitiesByName_thenCorrectlySorted() { Listhumans = 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))); }
Мы можем упростить приведенный выше пример еще больше, если мы используем Comparator.comparing() метод :
@Test public final void givenStreamComparatorOrdering_whenSortingEntitiesByName_thenCorrectlySorted() { Listhumans = 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()
Мы также можем использовать Поток.сортировка() чтобы отсортировать коллекцию в обратном порядке.
Во-первых, давайте рассмотрим пример того, как объединить метод sorted() с методом Comparator.ReverseOrder() для сортировки списка в обратном естественном порядке :
@Test public final void givenStreamNaturalOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() { Listletters = Lists.newArrayList("B", "A", "C"); List reverseSortedLetters = letters.stream() .sorted(Comparator.reverseOrder()) .collect(Collectors.toList()); assertThat(reverseSortedLetters.get(0), equalTo("C")); }
Теперь давайте посмотрим, как мы можем использовать метод sorted() и пользовательский компаратор :
@Test public final void givenStreamCustomOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() { Listhumans = 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 переворачивается, что и делает обратное.
Наконец, давайте упростим приведенный выше пример, используя метод Comparator.comparing () |/:
@Test public final void givenStreamComparatorOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted() { Listhumans = 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. Нулевые значения
До сих пор мы реализовали наш Компаратор s таким образом, что они не могут сортировать коллекции, содержащие null значения. То есть, если коллекция содержит хотя бы один элемент null , то метод sort вызывает исключение NullPointerException :
@Test(expected = NullPointerException.class) public void givenANullElement_whenSortingEntitiesByName_thenThrowsNPE() { Listhumans = Lists.newArrayList(null, new Human("Jack", 12)); humans.sort((h1, h2) -> h1.getName().compareTo(h2.getName())); }
Самым простым решением является обработка значений null вручную в нашей реализации Comparator :
@Test public void givenANullElement_whenSortingEntitiesByNameManually_thenMovesTheNullToLast() { Listhumans = 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)); }
Здесь мы перемещаем все элементы null в конец коллекции. Для этого компаратор считает, что null больше ненулевых значений. Когда оба null , они считаются равными.
Кроме того, мы можем передать любой Компаратор , который не является безопасным для null, в метод Comparator.nullsLast () | и получить тот же результат :
@Test public void givenANullElement_whenSortingEntitiesByName_thenMovesTheNullToLast() { Listhumans = 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)); }
Аналогично, мы можем использовать Comparator.nullsFirst() для перемещения элементов null к началу коллекции:
@Test public void givenANullElement_whenSortingEntitiesByName_thenMovesTheNullToStart() { Listhumans = 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)); }
Настоятельно рекомендуется использовать сначала нули() или нули Последние() декораторы, поскольку они более гибкие и, прежде всего, более читабельные.
13. Заключение
В этой статье проиллюстрированы различные и захватывающие способы сортировки списка с помощью лямбда – выражений Java 8 – переход от синтаксического сахара к реальной и мощной функциональной семантике.
Реализацию всех этих примеров и фрагментов кода можно найти на GitHub .