«1. Обзор

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

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

2. Использование Stream 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 в нашем проекте:

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

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

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

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.