JDK 16 now includes a toList()
method directly on Stream
instances. In previous Java versions, you always had to use the collect
method and provide a Collector
instance.
The new method is obviously fewer characters to type. Are both methods interchangeable or are there subtle differences one should be aware of?
var newList = someCollection.stream()
.map(x -> mapX(x))
.filter(x -> filterX(x))
.toList();
// vs.
var oldList = someCollection.stream()
.map(x -> mapX(x))
.filter(x -> filterX(x))
.collect(Collectors.toList());
(This question is similar to Would Stream.toList() perform better than Collectors.toList(), but focused on behavior and not (only) on performance.)
Collectors toList() method in Java with ExamplesIt returns a Collector Interface that gathers the input data onto a new list. This method never guarantees type, mutability, serializability, or thread-safety of the returned list but for more control toCollection(Supplier) method can be used.
For instance, Collectors. toList() method can return an ArrayList or a LinkedList or any other implementation of the List interface. To get the desired Collection, we can use the toCollection() method provided by the Collectors class.
toList(), collects the elements into an unmodifiable List. Though the current implementation of the Collectors. toList() creates a mutable List, the method's specification itself makes no guarantee on the type, mutability, serializability, or thread-safety of the List.
Collector. toList() will return an empty List for you. As you can see ArrayList::new is being used as a container for your items.
One difference is that Stream.toList()
provides a List
implementation that is immutable (type ImmutableCollections.ListN
that cannot be added to or sorted) similar to that provided by List.of()
and in contrast to the mutable (can be changed and sorted) ArrayList
provided by Stream.collect(Collectors.toList())
.
Demo:
import java.util.stream.Stream;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> list = Stream.of("Hello").toList();
System.out.println(list);
list.add("Hi");
}
}
Output:
[Hello]
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.ImmutableCollections.uoe(ImmutableCollections.java:142)
at java.base/java.util.ImmutableCollections$AbstractImmutableCollection.add(ImmutableCollections.java:147)
at Main.main(Main.java:8)
Please check this article for more details.
Interestingly, Stream.toList()
returns a null
s-containing list successfully.
import java.util.stream.Stream;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Object> list = Stream.of(null, null).toList();
System.out.println(list);
}
}
Output:
[null, null]
On the other hand, List.of(null, null)
throws NullPointerException
.
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Object> list = List.of(null, null);
}
}
Output:
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:208)
at java.base/java.util.ImmutableCollections$List12.<init>(ImmutableCollections.java:453)
at java.base/java.util.List.of(List.java:827)
at Main.main(Main.java:5)
Note: I've used openjdk-16-ea+34_osx-x64 to compile and execute the Java SE 16 code.
Useful resources:
Here is a small table that summarizes the differences between Stream.collect(Collectors.toList())
, Stream.collect(Collectors.toUnmodifiableList())
and Stream.toList()
:
Method | Guarantees unmodifiability | Allows nulls |
---|---|---|
collect(toList()) |
No | Yes |
collect(toUnmodifiableList()) |
Yes | No |
toList() |
Yes | Yes |
Another small difference:
// Compiles
List<CharSequence> list = Stream.of("hello", "world").collect(toList());
// Error
List<CharSequence> list = Stream.of("hello", "world").toList();
.collect(toList())
and toList()
behave different regarding sub type compatibility of the elements in the created lists.
Have a look at the following alternatives:
List<Number> old = Stream.of(0).collect(Collectors.toList());
works fine, although we collect a stream of Integer into a list of Number.List<Number> new = Stream.of(0).toList();
is the equivalent Java 16+ version, but it doesn't compile (cannot convert from List<Integer> to List<Number>
; at least in ecj, the Eclipse Java compiler).There are at least 2 workarounds to fix the compile error:
List<Number> fix1 = Stream.of(0).map(Number.class::cast).toList();
List<? extends Number> fix2 = Stream.of(0).toList();
To my understanding the root cause is as follows: The generic type T of the Java 16 toList()
is the same as the generic type T of the Stream itself. The generic type T of Collectors.toList() however is propagated from the left hand side of the assignment. If those 2 types are different, you may see errors when replacing all the old calls.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With