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