Перейти к основному содержимому

DistinctBy в Java Stream API

· 3 мин. чтения

1. Обзор

Поиск различных элементов в списке — одна из распространенных задач, с которыми мы, программисты, обычно сталкиваемся. Начиная с Java 8, с включением потоков , у нас есть новый API для обработки данных с использованием функционального подхода.

В этой статье мы покажем различные альтернативы фильтрации коллекции с использованием определенного атрибута объектов в списке.

2. Использование потокового API

Stream API предоставляет метод different () , возвращающий различные элементы списка на основе метода equals() класса Object .

Однако он становится менее гибким, если мы хотим фильтровать по определенному атрибуту. Одна из имеющихся у нас альтернатив — написать фильтр, сохраняющий состояние.

2.1. Использование фильтра с отслеживанием состояния

Одним из возможных решений может быть реализация предиката с отслеживанием состояния:

public static <T> Predicate<T> distinctByKey(
Function<? super T, ?> keyExtractor) {

Map<Object, Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}

Чтобы проверить это, мы будем использовать следующий класс Person с атрибутами age , email и name:

public class Person { 
private int age;
private String name;
private String email;
// standard getters and setters
}

И чтобы получить новую отфильтрованную коллекцию по имени , мы можем использовать:

List<Person> personListFiltered = personList.stream() 
.filter(distinctByKey(p -> p.getName()))
.collect(Collectors.toList());

3. Использование коллекций Eclipse

Eclipse Collections — это библиотека, предоставляющая дополнительные методы для обработки потоков и коллекций в Java.

3.1. Использование ListIterate.distinct()

Метод ListIterate.distinct() позволяет нам фильтровать поток , используя различные стратегии хеширования. Эти стратегии можно определить с помощью лямбда-выражений или ссылок на методы.

Если мы хотим отфильтровать по имени человека :

List<Person> personListFiltered = ListIterate
.distinct(personList, HashingStrategies.fromFunction(Person::getName));

Или, если атрибут, который мы собираемся использовать, является примитивным (int, long, double), мы можем использовать специализированную функцию, подобную этой:

List<Person> personListFiltered = ListIterate.distinct(
personList, HashingStrategies.fromIntFunction(Person::getAge));

3.2. Зависимость от Maven

Нам нужно добавить следующие зависимости в наш pom.xml , чтобы использовать Eclipse Collections в нашем проекте:

<dependency> 
<groupId>org.eclipse.collections</groupId>
<artifactId>eclipse-collections</artifactId>
<version>8.2.0</version>
</dependency>

Вы можете найти последнюю версию библиотеки коллекций Eclipse в репозитории Maven Central .

Чтобы узнать больше об этой библиотеке, мы можем перейти к этой статье .

4. Использование Vavr ( Javaslang )

Это функциональная библиотека для Java 8, предоставляющая неизменяемые данные и структуры функционального управления.

4.1. Использование List.distinctBy

Для фильтрации списков этот класс предоставляет свой собственный класс List, который имеет метод DifferentBy() , который позволяет нам фильтровать по атрибутам содержащихся в нем объектов:

List<Person> personListFiltered = List.ofAll(personList)
.distinctBy(Person::getName)
.toJavaList();

4.2. Зависимость от Maven

Мы добавим следующие зависимости в наш pom.xml , чтобы использовать Vavr в нашем проекте.

<dependency> 
<groupId>io.vavr</groupId>
<artifactId>vavr</artifactId>
<version>0.9.0</version>
</dependency>

Вы можете найти последнюю версию библиотеки Vavr в репозитории Maven Central .

Чтобы узнать больше об этой библиотеке, мы можем перейти к этой статье .

5. Использование StreamEx

Эта библиотека предоставляет полезные классы и методы для обработки потоков Java 8.

5.1. Использование StreamEx.distinct

В предоставленных классах есть StreamEx , у которого есть отдельный метод, которому мы можем отправить ссылку на атрибут, который мы хотим выделить:

List<Person> personListFiltered = StreamEx.of(personList)
.distinct(Person::getName)
.toList();

5.2. Зависимость от Maven

Мы добавим следующие зависимости в наш pom.xml , чтобы использовать StreamEx в нашем проекте.

<dependency> 
<groupId>one.util</groupId>
<artifactId>streamex</artifactId>
<version>0.6.5</version>
</dependency>

Вы можете найти последнюю версию библиотеки StreamEx в репозитории Maven Central .

6. Заключение

В этом кратком руководстве мы рассмотрели примеры того, как получить различные элементы потока на основе атрибута с использованием стандартного API Java 8 и дополнительных альтернатив с другими библиотеками.

Как всегда, полный код доступен на GitHub .