DistinctBy in the Java Stream API

1. Overview

Searching for different elements in a list is one of the common tasks that we as programmers usually face. From Java 8 on with the inclusion of Streams we have a new API to process data using functional approach.

In this article, we’ll show different alternatives to filtering a collection using a particular attribute of objects in the list.

2. Using the Stream API

The Stream API provides the distinct() method that returns different elements of a list based on the equals() method of the Object class.

However, it becomes less flexible if we want to filter by a specific attribute. One of the alternatives we have is to write a filter that maintains the state.

2.1. Using a Stateful Filter

One of the possible solutions would be to implement a stateful Predicate:

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; 
}

To test it, we’ll use the following Person class that has the attributes ageemail, and name:

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

And to get a new filtered collection by name, we can use:

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

3. Using Eclipse Collections

Eclipse Collections is a library that provides additional methods for processing Streams and collections in Java.

3.1. Using the ListIterate.distinct()

The ListIterate.distinct() method allows us to filter a Stream using various HashingStrategies. These strategies can be defined using lambda expressions or method references.

If we want to filter by the Person’s name:

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

Or, if the attribute we are going to use is primitive (int, long, double), we can use a specialized function like this:

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

3.2. Maven Dependency

We need to add the following dependencies to our pom.xml to use Eclipse Collections in our project:

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

You can find the latest version of the Eclipse Collections library in the Maven Central repository.

To learn more about this library we can go to this article.

4. Using Vavr (Javaslang)

This is a functional library for Java 8 that provides immutable data and functional control structures.

4.1. Using List.distinctBy

To filter lists, this class provides its own List class which has the distinctBy() method that allows us to filter by attributes of the objects it contains:

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

4.2. Maven Dependency

We will add the following dependencies to our pom.xml to use Vavr in our project.

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

You can find the latest version of the Vavr library in the Maven Central repository.

To learn more about this library we can go to this article.

5. Using StreamEx

This library provides useful classes and methods for Java 8 streams processing.

5.1. Using StreamEx.distinct

Within the classes provided is StreamEx which has the distinct method to which we can send a reference to the attribute where we want to distinct:

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

5.2. Maven Dependency

We will add the following dependencies to our pom.xml to use StreamEx in our project.

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

You can find the latest version of the StreamEx library in the Maven Central repository.

6. Conclusion

In this quick tutorial, we explored examples of how to get different elements of a Stream, based on an attribute using the standard Java 8 API and additional alternatives with other libraries.

As always, the complete code is available over on GitHub.

Related posts:

Các nguyên lý thiết kế hướng đối tượng – SOLID
Java Program to Find MST (Minimum Spanning Tree) using Prim’s Algorithm
Inheritance and Composition (Is-a vs Has-a relationship) in Java
Jackson – Unmarshall to Collection/Array
Spring Boot - Enabling Swagger2
Check If Two Lists are Equal in Java
Java Program to Implement Stack using Linked List
Java Program to Compute Discrete Fourier Transform Using the Fast Fourier Transform Approach
Remove All Occurrences of a Specific Value from a List
XML-Based Injection in Spring
Java Program to Implement Tarjan Algorithm
Java Program to Find the Shortest Path from Source Vertex to All Other Vertices in Linear Time
How to Define a Spring Boot Filter?
Java Program to Implement Patricia Trie
Java Program to Implement Suffix Array
Difference Between Wait and Sleep in Java
Supplier trong Java 8
Spring 5 Functional Bean Registration
Làm thế nào tạo instance của một class mà không gọi từ khóa new?
Java Program to Implement EnumMap API
Java Program to Implement Warshall Algorithm
Java Program to Check whether Graph is Biconnected
Inject Parameters into JUnit Jupiter Unit Tests
Java Program to Find the Vertex Connectivity of a Graph
Java Program to Implement Hash Tree
Jackson – Bidirectional Relationships
Java InputStream to String
Overview of Spring Boot Dev Tools
Spring Security Registration – Resend Verification Email
Java Program to Generate All Subsets of a Given Set in the Lexico Graphic Order
Spring Boot - Actuator
Generating Random Dates in Java