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() {
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. Базовая Сортировка С Поддержкой Лямбды
С введением лямбд мы теперь можем обойти анонимный внутренний класс и достичь того же результата с помощью простой функциональной семантики :
(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)));
} Обратите внимание, что мы также используем новый sort API, добавленный в java.util.Список в Java 8 – вместо старых коллекций.сортировка API.
4. Базовая Сортировка Без Определений Типов
Мы можем еще больше упростить выражение, не указывая определения типов – компилятор способен вывести их самостоятельно:
(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 :
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() {
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. Сортировка Извлеченных Компараторов
Мы также можем избежать определения даже самой логики сравнения, используя ссылку на метод экземпляра и метод Comparator.comparing , который извлекает и создает Comparable на основе этой функции.
Мы собираемся использовать getter 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 также представил вспомогательный метод для реверсирования компаратора – мы можем быстро использовать его для реверсирования нашей сортировки:
@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. Сортировка С Несколькими Условиями
Сравнение лямбда – выражений не обязательно должно быть таким простым – мы можем написать более сложные выражения, а также – например, сортировка сущностей сначала по имени, а затем по возрасту:
@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 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() {
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()
Мы также можем сортировать коллекцию с помощью Java 8 Течение отсортированный() ИНТЕРФЕЙС ПРИКЛАДНОГО ПРОГРАММИРОВАНИЯ.
Мы можем сортировать поток, используя естественный порядок, а также порядок, предоставляемый компаратором . Для этого у нас есть два перегруженных варианта sorted() API:
- сортировка ed() – сортирует элементы потока с использованием естественного упорядочения; класс элементов должен реализовать интерфейс Comparable .
- сортировка(Comparator super T> compa rator) – сортировка элементов на основе экземпляра Comparator
Давайте рассмотрим пример того, как использовать метод sorted() с естественным упорядочением :
@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"));
} Теперь давайте посмотрим, как мы можем использовать пользовательский компаратор с 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)));
} Мы можем упростить приведенный выше пример еще больше, если мы используем Comparator.comparing() метод :
@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()
Мы также можем использовать Поток.сортировка() чтобы отсортировать коллекцию в обратном порядке.
Во-первых, давайте рассмотрим пример того, как объединить метод sorted() с методом Comparator.ReverseOrder() для сортировки списка в обратном естественном порядке :
@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"));
} Теперь давайте посмотрим, как мы можем использовать метод sorted() и пользовательский компаратор :
@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 переворачивается, что и делает обратное.
Наконец, давайте упростим приведенный выше пример, используя метод Comparator.comparing () |/:
@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. Нулевые значения
До сих пор мы реализовали наш Компаратор s таким образом, что они не могут сортировать коллекции, содержащие null значения. То есть, если коллекция содержит хотя бы один элемент null , то метод sort вызывает исключение NullPointerException :
@Test(expected = NullPointerException.class)
public void givenANullElement_whenSortingEntitiesByName_thenThrowsNPE() {
List humans = Lists.newArrayList(null, new Human("Jack", 12));
humans.sort((h1, h2) -> h1.getName().compareTo(h2.getName()));
} Самым простым решением является обработка значений null вручную в нашей реализации Comparator :
@Test
public void givenANullElement_whenSortingEntitiesByNameManually_thenMovesTheNullToLast() {
List 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));
} Здесь мы перемещаем все элементы null в конец коллекции. Для этого компаратор считает, что null больше ненулевых значений. Когда оба null , они считаются равными.
Кроме того, мы можем передать любой Компаратор , который не является безопасным для null, в метод Comparator.nullsLast () | и получить тот же результат :
@Test
public void givenANullElement_whenSortingEntitiesByName_thenMovesTheNullToLast() {
List 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));
} Аналогично, мы можем использовать Comparator.nullsFirst() для перемещения элементов null к началу коллекции:
@Test
public void givenANullElement_whenSortingEntitiesByName_thenMovesTheNullToStart() {
List 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));
}
Настоятельно рекомендуется использовать сначала нули() или нули Последние() декораторы, поскольку они более гибкие и, прежде всего, более читабельные.
13. Заключение
В этой статье проиллюстрированы различные и захватывающие способы сортировки списка с помощью лямбда – выражений Java 8 – переход от синтаксического сахара к реальной и мощной функциональной семантике.
Реализацию всех этих примеров и фрагментов кода можно найти на GitHub .