Type mismatch with lambda - java

So the following code:
scoreCombiner = (Collection<ScoreContainer> subScores) -> subScores.parallelStream()
.mapToInt(ScoreContainer::getScore)
.reduce((a, b) -> a + b);
where scoreCombiner is a field declared as
private final ToIntFunction<? super List<? super ScoreContainer>> scoreCombiner;
is giving me the error Type mismatch: cannot convert from ToIntFunction<Collection<ScoreContainer>> to ToIntFunction<? super List<? super ScoreContainer>> which I can't understand. Collection is definitely a super-type of List, and ScoreContainer is, of course, a super-type of itself. Any help would be appreciated.

The ? super List is fine in this context.
For example this would compile:
ToIntFunction<? super List<?>> f = ( (Collection<?> c) -> 0 );
A ToIntFunction<? super List<?>> is a ToIntFunction that consumes a List<?>, which a ToIntFunction<Collection<?>> does.
The issue is the typing of the List/Collection. Recall now again, a List<? super ScoreContainer> is any List that accepts a ScoreContainer. So the trouble here is that an IntFunction<? super List<? super ScoreContainer>> accepts any List that accepts a ScoreContainer. So for example, you should be able to pass it a Collection<Object>.
You could only assign a lambda such as
... = (Collection<? super ScoreContainer> subScores) -> subScores...();
But the Collection your lambda is expecting is really a producer. You are expecting it to produce ScoreContainers (which you call getScore on). So you should be bounded by extends.
ToIntFunction<? super List<? extends ScoreContainer>> scoreCombiner;
... = (Collection<? extends ScoreContainer> subScores) -> subScores...();
What is PECS (Producer Extends Consumer Super)?

Related

Lower bounded wildcard method

Could anyone please let me understand why this piece of code is getting compiler error:
List<? super A> superList = new ArrayList<>();
superList.add(new Object());
While this one is perfectly correct:
setSuper(new ArrayList<Object>());
public void superMethod(List<? super A> list) {
//...
}
I understand that lower bound narrows available types to A or their descendants, but why this is fine for compiler when it comes to methods arguments?
An ArrayList<Object> is a valid value of type List<? super A>.
An ArrayList<A> is also a valid value of type List<? super A>.
But you can't add new Object() to an ArrayList<A>. So not all List<? super A> values can add an object, so the compiler correctly errors.

Why ListList<? super E> is List<? extends List<? super E>> but not List<List<? super E>>

I have a generic interface interface ListList<E> extends List<List<E>>. For some reasons, I can't cast ListList<? super T> to List<List<? super T>>. Is there any way of doing it and why it doesn't work?
By this moment I've already tried the following:
Simple assignment, this way I've managed to assign ListList<? super T> to List<? extends List<? super T>> (1), but when I try to assign ListList<? super T> to List<List<? super T>> I get Incompatible types compile-time error (1.1).
Explicit type conversion, it doesn't work because of the same Incompatible types compile-time error (2).
Casting to raw type ListList, it works (3), but I don't like raw types.
Adding all elements from ListList<? super T> to List<? extends List<? super T>>, it works (4), but I need a more general solution which works not only with ListList<E>, but with any generic type.
Here is my code:
ListList<? super T> var = new ArrayListList<>();
List<? extends List<? super T>> work = var; // (1)
List<List<? super T>> notWork = var; // (1.1)
List<List<? super T>> explicit = (List<List<? super T>>) var; // (2)
List<List<? super T>> raw = (ListList) var; // (3)
List<List<? super T>> copy = new ArrayList<>(); // (4)
copy.addAll(var); // (4)
I've expected ListList<? super T> to be List<List<? super T>>, but it appeared to be List<? extends List<? super T>>. I need to know why it is and how I can cast it to List<List<? super T>> without raw types and copying of elements.
At first, it looks like these assignments should all succeed, but they don't because of the inner wildcard ? super T. If we remove those wildcards, then all the assignments compile.
public static <T> void test() {
ListList<T> var = new ArrayListList<>();
List<? extends List<T>> work = var; // Compiles
List<List<T>> notWork = var; // Compiles
List<List<T>> explicit = (List<List<T>>) var; // Compiles
List<List<T>> raw = (ListList) var; // Compiles with warning
List<List<T>> copy = new ArrayList<>(); // Compiles
copy.addAll(var); // Compiles
}
I still get the unchecked conversion warning for (3), but they all still compile.
At first glance it looks like declaring the interface
ListList<E> extends List<List<E>>
makes a ListList equivalent to a List of Lists. However what you have done is take a nested type parameter and made it the main type parameter. The reason that that makes a difference is nested wildcards don't perform wildcard capture.
A nested wildcard here means "a list of lists of any type matching the bound", but a main-level wildcard here means "a 'listlist' of a specific yet unknown type matching the bound".
One cannot add an object that is a supertype of the lower bound to a collection, because the type parameter -- a specific yet unknown type -- may be the actual bound.
List<? super Integer> test2 = new ArrayList<>();
test2.add(2); // Compiles; can add 2 if type parameter is Integer, Number, or Object
test2.add((Number) 2); // Error - Can't add Number to what could be Integer
test2.add(new Object()); // Error - Can't add Object to what could be Integer
Because Java's generics are invariant, the types must match exactly when type parameters are involved, so similar cases for ListList all fail to compile.
// My assumption of how your ArrayListList is defined.
class ArrayListList<E> extends ArrayList<List<E>> implements ListList<E> {}
ListList<? super Integer> llOfSuperI = new ArrayListList<>();
llOfSuperI.add(new ArrayList<Integer>()); // capture fails to match Integer
llOfSuperI.add(new ArrayList<Number>()); // capture fails to match Number
llOfSuperI.add(new ArrayList<Object>()); // capture fails to match Object
However, a List of List compiles with all 3 cases.
List<List<? super Integer>> lOfLOfSuperI = new ArrayList<>();
lOfLOfSuperI.add(new ArrayList<Integer>()); // no capture; compiles
lOfLOfSuperI.add(new ArrayList<Number>()); // no capture; compiles
lOfLOfSuperI.add(new ArrayList<Object>()); // no capture; compiles
Your ListList is a different type than a List of Lists, but the differing generics behavior of where the type parameter is defined means that there is different generics behavior. This is why you cannot directly assign a ListList<? super T> to a List<List<? super T>> (1.1), and also why you can't cast it (2). You can cast to a raw type to get it to compile (3), but that introduces the possibilities of ClassCastException in future use of the casted object; that is what the warning is about. You can assign it to a List<? extends List<? super T>> (1), introducing another wildcard to capture a subtype relationship, but that introduces a wildcard to be captured; you won't be able to add anything useful to that list.
These differences have arisen only because a wildcard introduces wildcard capture and the associated differences. Without using a wildcard, a ListList<E> is equivalent to a List<List<E>> and as shown at the top of this answer, shows no problems compiling the code.
If you want all of your sub-lists to use the same exact type parameter, then go ahead and use your ListList interface, but don't use any wildcards. This forces the exact same type parameter for all lists that are added to your ListList, i.e. a ListList<Integer> can only hold List<Integer>s.
If you want all of your sub-lists to simply match a wildcard, e.g. contain a List<Number>, List<Integer>, and List<Object> in the same list, then just use a List<List<? super T>> to avoid wildcard capture.

Using addAll() on List<? extends E> a and b doesn't work

Why doesn't this compile?
List<? extends Number> a = new ArrayList<>();
List<? extends Number> b = new ArrayList<>();
a.addAll(b);
Because it wouldn't be safe.
List<? extends Number> should be read as some list where the element type extends Number. So in runtime a could be a List<Long> and b could be a List<BigInteger>. In that case, a.addAll(b) would mean "add all BigIntegers to the list of Longs" which, if allowed, obviously wouldn't be type safe.
https://google.github.io/guava/releases/19.0/api/docs/com/google/common/collect/Iterables.html
What really worked for me was Guava:
com.google.common.collect.Iterables.concat(...)
List<? extends Number> means
Items in this List have all the same class. Not only do they extend
Number, but are all the same type.
Due to type erasure during compile, the byte code interpreter does not know (and cannot infer), whether the ? of a and the ? refer to the same class.

Java type inference with lower bounded types

Why is it that Java can infer the common ancestor of multiple upper-bounded types, but not of lower-bounded types?
More specifically, consider the following examples:
static class Test {
static <T> T pick(T one, T two) {
return two;
}
static void testUpperBound() {
List<? extends Integer> extendsInteger = new ArrayList<>();
// List<? extends Integer> is treated as a subclass of List<? extends Number>
List<? extends Number> extendsNumber = extendsInteger;
// List<? extends Number> is inferred as the common superclass
extendsNumber = pick(extendsInteger, extendsNumber);
}
static void testLowerBound() {
List<? super Number> superNumber = new ArrayList<>();
// List<? super Number> is treated as a subclass of List<? super Integer>
List<? super Integer> superInteger = superNumber;
// The inferred common type should be List<? super Integer>,
// but instead we get a compile error:
superInteger = pick(superNumber, superInteger);
// It only compiles with an explicit type argument:
superInteger = Test.<List<? super Integer>>pick(superNumber, superInteger);
}
}
I think I can explain why Java differentiates between a lower-bounded and upper-bounded type.
Trying to infer a common lower bound can fail when incompatible bounds are used, for example Integer and Long. When we're using an upper bound, it's always possible to find some common upper bound, in this case List<? extends Number>. But there's no common lower bound of List<? super Integer> and List<? super Long>. The only safe option in case of such a conflict would be to return List<? extends Object>, synonymous with List<?>, meaning "a List of unknown type".
Now, arguably we could have resorted to that only when there actually are conflicting bounds, as opposed to the case in my question. But maybe it was decided to take the easy way out and not assume there's a common lower bound unless explicitly specified.
I'm using 1.8.0_25 and I'm getting the compile error.
The error, however, is not that the call to pick is bad, but the variable you want to put the result into.
Repeating your example:
static void testLowerBound() {
List<? super Number> superNumber = new ArrayList<>();
List<? super Integer> superInteger = superNumber;
// this gets the error
superInteger = pick(superNumber, superInteger);
// this doesn't
pick(superNumber, superInteger);
// what's happening behind is
List<? extends Object> behind = pick(superNumber, superInteger);
superInteger = behind;
// that last line gets the same compilation error
}
If you look at how T is being substituted in the call, the parameters are used as List, losing the information about the lower bound.
About inference: Every ? is not exactly "whatever that can be assigned to..." but "a particular type I don't want to name, that can be assigned to...". It matters because in your example you get 3 variables, 1 for each list and another, different one, for the result of pick.
Now, due to the declaration of pick, the substitution for T has to satisfy the class hierarchy of the parameters. In the first case you need a substitute for <#1 extends Integer> and <#2 extends Number>. #2 could be Double, so the best clue you've got is that #3 extends Number.
In the second case you need a substitute for <#1 super Integer> and <#2 super Number>. Now that means #2 could be anyone of Number, Object, Serializable; #1 adds to that list Comparable and Integer. The combinations could be Number, Object (and T should be Object); or Serializable, Integer (and T could be Serializable), so the best clue it has is that T is a List of an unknown type extending Object.
Of course it could only reach to Number, but you can't get two bounds for the same type variable, so has to let it at that

<? super E> and <? extends E> for List

Having the following simple class structure:
class A {
}
class B extends A {
}
class C extends B {
}
I'm creating an ArrayList to keep objects of the earlier created classes:
List<? extends A> list1 = new ArrayList<A>();
List<? extends B> list2 = new ArrayList<B>();
List<? extends C> list3 = new ArrayList<C>();
List<? super A> list4 = new ArrayList<A>();
List<? super B> list5 = new ArrayList<B>();
List<? super C> list6 = new ArrayList<C>();
To each of those lists I'm trying to add 1 object of each earlier created class: A,B,C. The only possible combination is:
adding object of class A,B,C to list4
adding object of class B and C to list5
adding object of class C to list list6.
The rest of the tries gives compiler errors, such us:
The method add(capture#1-of ? extends
A) in the type List is not applicable for the
arguments (A)
Why can't I add any object of class A,B,C to list1/2/3?
Why e.g. list4 accepts objects of classes A,B,C if they are supposed to be a super class of class A, as the list4 is defined?
"? extends A" means "some type derived from A (or A itself)". So for instance, a List<ByteArrayOutputStream> is compatible with List<? extends OutputStream> - but you shouldn't be able to add a FileOutputStream to such a list - it's meant to be a List<ByteArrayOutputStream>! All you know is that anything you fetch from the list will be an OutputStream of some kind.
"? super A" means "some type which is a superclass of A (or A itself)". So for instance, a List<OutputStream> is compatible with List<? super ByteArrayOutputStream>. You can definitely add a ByteArrayOutputStream to such a list - but if you fetch an item from the list, you can't really guarantee much about it.
See Angelika Langer's Generics FAQ for much more information.
The type definition List<? extends A> is not usable for a mutable List - the explanation given in Java generics Java Generics Pdf is
The add() method takes arguments of type E, the element type of the
collection.
When the actual type parameter is ?, it stands for some unknown type.
Any parameter
we pass to add would have to be a subtype of this unknown type. Since we
don’t know
what type that is, we cannot pass anything in.
However, when the typedef is List<? super A> then the type parameter ? is implicitly typed.
List<? extends A> list1
It is a list, whose type element could be any unknown subclass of A. For example, it could be a D subclass. Therefore, you can't add anything to it, it could be wrong...
List<? super A> list4
It is a list, whose type element could be A, or a superclass of A (does not exist in that case, except Object). Therefore, you can add A objects to it, or any subclass of A such as B or C.
It doesn't work that way.
You should use <? extends T> when you create function which argument is collection of unknown subtype of some type, and you want to fetch objects from that collection:
int sum(Collection<? extends Integer> collection) {
for (Integer : collection) {
// do sth
}
// ...
}
You cannot add new items to this collection, because you don't know which concrete type is this collection holding. All you know is that that type extends Integer.
You use <? super T> when you want to add new items of type T to collection and return that collection, but then you cannot guarantee what you can retrieve from it and you have to cast result of get() or check its type. You can safely add items of type T and subtypes, and retrieve items of type T.
List<? super B> allows you to use Lists of any supertype of B, i.e. list5 = new ArrayList<A>();
or list5 = new ArrayList<Object>();
You can safely add B (and subtypes) to every list that use supertypes of B, but you can not add any supertype of B. Imagine this:
public void myAdd(List<? super B> lst) {
lst.add(new Object()) //this is a supertype of B (compile time error)
}
...
ArrayList<A> list = new ArrayList<A>();
myAdd(list); //tries to add Object to a list of type A

Categories

Resources