Collect a Java Stream to an Immutable Collection

1. Introduction

In this quick article, we’re going to have a look at various ways of collecting Java Streams to immutable Collections – which require a special approach because standard Collectors work only with mutable data structures.

2. Maven Dependency

We are going to make use of the Google’s Guava library to drive some of our examples:

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

We can get the latest version of this dependency from here.

3. Using Java’s collectingAndThen()

The collectingAndThen() method from Java’s Collectors class accepts a Collector, and a finisher Function that is applied to the result returned from the Collector:

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

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

Since we can’t use the toCollection() Collector directly, we need to collect elements to a temporary list and then construct an immutable list from it.

In this example, we are converting a Stream to a List using the toList() collector and then creating an ImmutableList. The ImmutableList is a part of the Guava library. If we log the output to the console, we’ll get the class of the underlying

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, with every immutable class comes an accompanying Collector that is as easy to use as standard Collectors:

@Test
public void whenCollectToImmutableList_thenSuccess() {
    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

Now, let’s move one step further and implement our custom Collector. To achieve this goal, we are going to 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);
}

To learn more about implementing custom Collectors, please have a look at Section 4 of this article. And that’s it. The above method is a part of our custom class

We can use it now just like any other built-in Collectors:

@Test
public void whenCollectToMyImmutableListCollector_thenSuccess() {
    List<String> givenList = Arrays.asList("a", "b", "c", "d");
    List<String> result = givenList.stream()
      .collect(MyImmutableListCollector.toImmutableList());
}

Finally, let’s check the output:

class java.util.Collections$UnmodifiableRandomAccessList

5.1. Making the MyImmutableListCollector Generic

Our implementation has one limitation – it always returns an immutable instance backed by an ArrayList. However, with 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);
}

Now instead of determining the supplier in the method implementation, we are requesting the supplier from the user:

@Test
public void whenPassingSupplier_thenSuccess() {
    List<String> givenList = Arrays.asList("a", "b", "c", "d");
    List<String> result = givenList.stream()
      .collect(MyImmutableListCollector.toImmutableList(LinkedList::new));
}

Please note that we are now using the LinkedList instead of ArrayList. Let’s run this and see the results:

class java.util.Collections$UnmodifiableList

This time, we got UnmodifiableList instead of UnmodifiableRandomAccessList.

6. Conclusion

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

Make sure to check out the full source code of this article over on GitHub.

Leave a Reply

Your email address will not be published.