Collect a Java Stream to an Immutable Collection

1. Introduction

We often wish to convert a Java Stream into a collection. This usually results in a mutable collection, but we can customize it.

In this short tutorial, we’re going to take a close look at how to collect a Java Stream to an immutable collection – first using plain Java, and then using the Guava library.

2. Using Standard Java

2.1. Using Java’s toUnmodifiableList

Starting with Java 10, we can use the toUnmodifiableList method from Java’s Collectors class:

List<String> givenList = Arrays.asList("a", "b", "c");
List<String> result = givenList.stream()
  .collect(toUnmodifiableList());

By using this method, we get a List implementation that doesn’t support null values from Java’s ImmutableCollections:

class java.util.ImmutableCollections$ListN

2.2. Using Java’s collectingAndThen

The collectingAndThen method from Java’s Collectors class accepts a Collector and a finisher Function. This finisher is applied to the result returned from the Collector:

List<String> givenList = Arrays.asList("a", "b", "c");
List<String> result = givenList.stream()
  .collect(collectingAndThen(toList(), ImmutableList::copyOf));

System.out.println(result.getClass());

With this approach, since we can’t use the toCollection Collector directly, we need to collect elements into a temporary list. Then, we construct an immutable list from it.

2.3. Using Stream.toList() method

Java 16 introduces a new method on Stream API called toList(). This handy method returns an unmodifiable List containing the stream elements:

@Test
public void whenUsingStreamToList_thenReturnImmutableList() {
    List<String> immutableList = Stream.of("a", "b", "c", "d").toList();
	
    Assertions.assertThrows(UnsupportedOperationException.class, () -> {
        immutableList.add("e");
    });
}

As we can see in the unit test, Stream.toList() returns an immutable listSo, trying to add a new element to the list will simply lead to UnsupportedOperationException.

Please bear in mind that the new Stream.toList() method is slightly different from the existing Collectors.toList() as it returns an unmodifiable list.

3. Building a Custom Collector

We also have the option to implement a custom Collector.

3.1. A Basic Immutable Collector

To achieve this, we can use the static Collector.of method:

public static <T> Collector<T, List<T>, List<T>> toImmutableList() {
    return Collector.of(ArrayList::new, List::add,
      (left, right) -> {
        left.addAll(right);
        return left;
      }, Collections::unmodifiableList);
}

We can use this function just like any built-in Collector:

List<String> givenList = Arrays.asList("a", "b", "c", "d");
List<String> result = givenList.stream()
  .collect(MyImmutableListCollector.toImmutableList());

Finally, let’s check the output type:

class java.util.Collections$UnmodifiableRandomAccessList

3.2. Making the MyImmutableListCollector Generic

Our implementation has one limitation – it always returns an immutable instance backed by an ArrayList. However, with a slight improvement, we can make this collector return a user-specified type:

public static <T, A extends List<T>> Collector<T, A, List<T>> toImmutableList(
  Supplier<A> supplier) {
 
    return Collector.of(
      supplier,
      List::add, (left, right) -> {
        left.addAll(right);
        return left;
      }, Collections::unmodifiableList);
}

So now, instead of determining the Supplier in the method implementation, we’re requesting the Supplier from the user:

List<String> givenList = Arrays.asList("a", "b", "c", "d");
List<String> result = givenList.stream()
  .collect(MyImmutableListCollector.toImmutableList(LinkedList::new));

Also, we’re using the LinkedList instead of ArrayList.

class java.util.Collections$UnmodifiableList

This time, we got UnmodifiableList instead of UnmodifiableRandomAccessList.

4. Using Guava’s Collectors

In this section, we’re going to use the Google Guava library to drive some of our examples:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>22.0</version>
</dependency>

Starting with Guava 21, every immutable class comes with an accompanying Collector that’s as easy to use as Java’s standard Collectors:

List<Integer> list = IntStream.range(0, 9)
  .boxed()
  .collect(ImmutableList.toImmutableList());

The resulting instance is the RegularImmutableList:

class com.google.common.collect.RegularImmutableList

5. Conclusion

In this short article, we’ve seen various ways to collect a Stream into an immutable Collection.

As always, the full source code of this article is over on GitHub. They’re separated by Java version into examples for sections 3-4, section 2.2, and section 2.3.

Related posts:

Java Program to Implement Find all Forward Edges in a Graph
Hướng dẫn Java Design Pattern – Abstract Factory
Java Program to Generate Date Between Given Range
Creating a Generic Array in Java
Java equals() and hashCode() Contracts
Introduction to Spring Cloud Rest Client with Netflix Ribbon
Format ZonedDateTime to String
Java Program to Test Using DFS Whether a Directed Graph is Weakly Connected or Not
Phương thức tham chiếu trong Java 8 – Method References
Java Program to Implement Insertion Sort
Java Program to Check Multiplicability of Two Matrices
Spring Data JPA Delete and Relationships
TreeSet và sử dụng Comparable, Comparator trong java
Java Program to Implement Threaded Binary Tree
Receive email using IMAP
Java Program to Find the Vertex Connectivity of a Graph
Java Program to Check for balanced parenthesis by using Stacks
Hướng dẫn Java Design Pattern – Mediator
A Guide to the finalize Method in Java
Spring Data JPA and Null Parameters
Model, ModelMap, and ModelAndView in Spring MVC
Java Program to Perform Arithmetic Operations on Numbers of Size
Hướng dẫn Java Design Pattern – Chain of Responsibility
Derived Query Methods in Spring Data JPA Repositories
Mapping Nested Values with Jackson
Java Program to Check if a Given Binary Tree is an AVL Tree or Not
Entity To DTO Conversion for a Spring REST API
A Comparison Between Spring and Spring Boot
Pagination and Sorting using Spring Data JPA
Java Program to Implement Triply Linked List
Java Program to Implement Wagner and Fisher Algorithm for online String Matching
Mệnh đề if-else trong java