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.