Converting a Java Stream into a List sounds trivial but can be surprising sometimes, especially when dealing with nulls and immutability.
Converting Stream<T> to List<T>
This is one of the most basic and mocked ways of converting Streams to Lists – I’m sure you know what I’m talking about, and I’m sure you’ve done this hundreds of times already:
stream().map(...).collect(Collectors.toList());
You might ask… what’s wrong with it? And the answer is: absolutely nothing.
However, you need to accept the fact that the result List is mutable(not enforced by any contract), supports null values, and that the syntax is overly verbose:
What "functional programming" in Java feels like pic.twitter.com/4Og3OxHTu9
— Lukas Eder (@lukaseder) September 4, 2015
Luckily, in JDK10 and JDK16, we got alternatives that deal with the mentioned issues… and also brought some confusion.
Converting Stream<T> to an unmodifiable List<T> (JDK10)
Since JDK10, converting a Stream instance into an unmodifiable List is as easy as applying a dedicated Collector:
List<Integer> l = Stream.of(1).collect(Collectors.toUnmodifiableList());
However, this is not as easy as replacing every Collectors.toList() call with Collectors.toUnmodifiableList(). It turns out that the Collector returns a List that does not support null values!
And we learn it the hard way:
Stream.of(1, 2, null).collect(Collectors.toUnmodifiableList()).add(2); // java.lang.NullPointerException // at java.base/java.util.Objects.requireNonNull(Objects.java:208)
If you want to have an unmodifiable List with null values support, you need to fall back to the JDK8 unmodifiable List trick:
List<Integer> l = Stream.of(1, 2, null) .collect(Collectors.collectingAndThen( Collectors.toList(), Collections::unmodifiableList));
However, if you’re on a JDK16+, there’s a better way.
Converting Stream<T> to an unmodifiable List<T> (JDK16)
Luckily, JDK16 brought in the most convenient of all options: Stream#toList.
This is not a Collector but a convenience method callable right on a Stream instance:
List<Integer> l = Stream.of(1, 2, null).toList();
The resulting List is unmodifiable and supports null values (however, this is not explicitly enforced by the contract).
But, I would be cautious when blindly replacing Collectors.toList() and Collectors.toUnmodifiableList() with it because of two reasons:
- Stream#toList returns an unmodifiable List instance, so if the List instance was passed around and modified, this would now result in an UnsupportedOperationException. Ouch!
- Stream#toList returns a List instance that supports null values
Unfortunately, IntelliJ IDEA will suggest converting from one to the other right away, which can backfire at times:
Summary
Two new additions to Stream API provide convenient ways of converting a Stream instance into a List.
However, those should be used carefully since they are not drop-in replacements due to slightly different semantics.