Java Top

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE

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'll see various ways of collecting a Java Stream to an immutable Collection.

2. Maven Dependency

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>

Our other examples come from the standard library.

3. 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.

Therefore, in this example, we're converting a Stream to a List using the toList collector and then creating an ImmutableList. The ImmutableList is a part of the Guava library.

Also, if we log the output to the console, we'll get the class of the underlying List implementation:

class com.google.common.collect.RegularImmutableList

4. Using Guava's Collectors

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. Building a Custom Collector

We also have the option to implement a custom Collector.

5.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

5.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.

6. 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

7. 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 2-5 and section 6.

Java bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Comments are closed on this article!