They differ in what they can process related to inheritance.
This answer really expands this one, since it will be too long for a comment.
Suppose there are two classes:
static class Animal {
}
static class Dog extends Animal {
}
And such a method:
List<Animal> test(List<Dog> dogs) {
Stream<Dog> stream = dogs.stream();
Collector<Animal, ?, List<Animal>> collector = Collectors.toList();
List<Animal> animals = stream.collect(collector);
return animals;
}
Notice a few probably obvious things:
stream
is of type Stream<Dog>
. The Stream.class
is declared as Stream<T> ...
, so T
will be inferred to be a Dog
.
stream::collect
calls a method that is declared as: <R, A> R collect(Collector<? super T, A, R> collector);
Notice the type: ? super T
, that is why we can pass a Collector<Animal, ?, List<Animal>
: the first argument Animal
is a super type of T
(Dog
).
This is kind of logic btw if you think what a collector is supposed to do. It must take some type and produce a List
of those types (in our case). That "type" in our case is a Dog
(but at the same time is an Animal
too). The result from the collector is a List<Animal>
and you can put dogs in such a List
.
The same thing does not work with toList
:
List<Animal> test(List<Dog> dogs) {
Stream<Dog> stream = dogs.stream();
List<Animal> animals = stream.toList();
return animals;
}
You probably understand why already, its because Stream::toList
is declared as : default List<T> toList() { ... }
.toList()
can never return a null-containing list, but.collect(toList()
can? i.e.Stream.of(null, null).toList()
throws,Stream.of(null, null).collect(toList())
returns? – Soul