Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Differences of Java 16's Stream.toList() and Stream.collect(Collectors.toList())?

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

like image 713
knittl Avatar asked Jan 30 '21 15:01

knittl


People also ask

What does collect collectors toList ()) do?

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.

Does collectors toList () return ArrayList?

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.

What implementation of List does the collectors toList () create?

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.

Does collectors toList return empty 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.


Video Answer


3 Answers

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.

Update:

Interestingly, Stream.toList() returns a nulls-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:

  1. JDK Bug#JDK-8180352
  2. Calling Java varargs method with single null argument?
like image 132
Arvind Kumar Avinash Avatar answered Oct 20 '22 02:10

Arvind Kumar Avinash


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();
like image 28
ZhekaKozlov Avatar answered Oct 20 '22 01:10

ZhekaKozlov


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

  • Explicitly cast to the wanted type List<Number> fix1 = Stream.of(0).map(Number.class::cast).toList();
  • allow sub types in the result collection: 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.

like image 2
Bananeweizen Avatar answered Oct 20 '22 00:10

Bananeweizen