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