Could someone help me to understand why this code behaves as described in the comments
// 1) compiles
List<Integer> l = Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, ArrayList<Integer>::addAll);
/*
* 2) does not compile
*
* Exception in thread "main" java.lang.Error: Unresolved compilation problems:
* Type mismatch: cannot convert from Object to <unknown>
* The type ArrayList does not define add(Object, Integer) that is applicable here
* The type ArrayList does not define addAll(Object, Object) that is applicable here
*/
Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
// 3) compiles
Stream.of(1, 2, 3).collect(ArrayList<Integer>::new, ArrayList::add, ArrayList::addAll);
/*
* 4) does not compile
*
* Exception in thread "main" java.lang.Error: Unresolved compilation problems:
* Type mismatch: cannot convert from Object to <unknown>
* The type ArrayList does not define add(Object, Integer) that is applicable here
* The type ArrayList<Integer> does not define addAll(Object, Object) that is applicable here
*/
Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, ArrayList<Integer>::addAll);
It has clearly something to do with the definition of a type in generic methods, it's an information that must be somehow provided... but why is it mandatory? Where and how, syntactically, should I have figured it out from the signature of methods of()
and collect()
?
public static<T> Stream<T> of(T... values) {...}
<R> R collect(Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner);
Although this is not an answer which analyzes the Lambda spec on http://download.oracle.com/otndocs/jcp/lambda-0_9_3-fr-eval-spec/index.html, I nevertheless tried to find out on what it depends.
Copying two methods from the Stream
class:
static class Stream2<T> {
@SafeVarargs
@SuppressWarnings("varargs") // Creating a stream from an array is safe
public static<T> Stream2<T> of(T... values) {
return new Stream2<>();
}
public <R> R collect( Supplier<R> supplier,
BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner){return null;}
}
This compiles:
Stream2.of(1,2,3).collect(ArrayList::new, ArrayList::add, ArrayList::addAll );
like OP's (2).
Now changing the collect
method to by moving the first argument to the third place
public <R> R collect(BiConsumer<R, ? super T> accumulator,
BiConsumer<R, R> combiner,
Supplier<R> supplier
){return null;}
This still works (5):
Stream2.of(1,2,3).collect(ArrayList::add, ArrayList::addAll,ArrayList::new );
Also this works (6):
Stream2.of(1,2,3).collect(ArrayList::add, ArrayList::addAll,ArrayList<Integer>::new );
These don't work (7,8):
Stream2.of(1,2,3).collect(ArrayList<Integer>::add, ArrayList::addAll,ArrayList::new );
Stream2.of(1,2,3).collect(ArrayList<Integer>::add, ArrayList<Integer>::addAll,ArrayList::new );
But this works again (9):
Stream2.of(1,2,3).collect(ArrayList<Integer>::add, ArrayList<Integer>::addAll,ArrayList<Integer>::new );
So i guess when a supplier is annotated with the explicit type argument, it seems to work. When only the consumers are, it does not. But maybe someone else knows why this makes a difference.
EDIT: Trying to use a TestList
, it gets even stranger:
public class StreamTest2 {
public static void main(String[] args) {
Stream.of(1, 2, 3).collect(TestList::new, TestList::add, TestList<Integer>::addAll);
Stream.of(1, 2, 3).collect(TestList::new, TestList::add, TestList<Integer>::addAll2);
Stream.of(1, 2, 3).collect(TestList::new, TestList::add, TestList<Integer>::addAll3);
Stream.of(1, 2, 3).collect(TestList::new, TestList::add, TestList<Integer>::addAll4);
Stream.of(1, 2, 3).collect(TestList::new, TestList::add, TestList<Integer>::addAll5);
Stream.of(1, 2, 3).collect(TestList::new, TestList::add, TestList<Integer>::addAll6);
}
}
class TestList<T> extends AbstractList<T> {
@Override
public T get(int index) {
return null;
}
@Override
public int size() {
return 0;
}
@Override
public boolean addAll(Collection<? extends T> c) {
return true;
}
public boolean addAll2(TestList<? extends T> c) {
return true;
}
public boolean addAll3(Collection<T> c) {
return true;
}
public boolean addAll4(List<? extends T> c) {
return true;
}
public boolean addAll5(AbstractList<? extends T> c) {
return true;
}
public boolean addAll6(Collection<? extends T> c) {
return true;
}
@Override
public boolean add(T e) {
return true;
}
}
addAll
does not work, but addAll2
-6
do work. Even addAll6
works, which has the same signature as the original addAll
.
Whenever you struggle about compiler errors, you should include, which compiler you have used and its version number. And if you have used a compiler other than the standard javac
, you should give javac
a try and compiler the results.
When you write
List<Integer> l = Stream.of(1, 2, 3)
.collect(ArrayList::new, ArrayList::add, ArrayList<Integer>::addAll);
the compiler will use the target type List<Integer>
for inferring the type R
(which matches exactly the target type here). Without a target type like in
Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
the compiler will infer the type R
from the supplier and infer ArrayList<Object>
instead. Since an ArrayList<Object>
is capable of holding Integer
instances and provides the necessary add
and addAll
methods, this construct compiles without problems when using the standard javac
. I tried jdk1.8.0_05
, jdk1.8.0_20
, jdk1.8.0_40
, jdk1.8.0_51
, jdk1.8.0_60
, jdk1.9.0b29
, and jdk1.9.0b66
to be sure that there are no version specific bugs involved. I guess, you are using Eclipse, which is known for having problems with the Java 8 type inference.
Similarly, using
Stream.of(1, 2, 3).collect(ArrayList<Integer>::new, ArrayList::add, ArrayList::addAll);
works but now your hint forces the inferred type for R
to be ArrayList<Integer>
. In contrast
Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, ArrayList<Integer>::addAll);
does not work as the compiler is inferring ArrayList<Object>
for the return type of the supplier which is not compatible with the method ArrayList<Integer>::addAll
. But the following would work:
Stream.of(1, 2, 3).collect(ArrayList::new, ArrayList::add, ArrayList<Object>::addAll);
However, you don’t need any explicit type when using standard javac
…
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