Lower bounded wildcard method - java

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.

Related

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

Type mismatch with lambda

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)?

Fancy generics capture collision

Please give me a hint as to what is going on here:
List<? extends Number> a = new ArrayList<Number>();
List<? extends Number> b = new ArrayList<Number>();
a.addAll(b); // ouch! compiler yells at me, see the block below:
/*
incompatible types
found : java.util.List<capture#714 of ? extends java.lang.Number>
required: java.util.List<java.lang.Number>
*/
This simple code does not compile. I vaguely remember something related to type captures, like those should be mostly used in interface specs, not the actual code, but I never got dumbfounded like that.
This of course might be fixed brute-forcefully, like that:
List<? extends Number> a = new ArrayList<Number>();
List<? extends Number> b = new ArrayList<Number>();
#SuppressWarnings({"unchecked"})
List<Number> aPlain = (List<Number>) a;
#SuppressWarnings({"unchecked"})
List<Number> bPlain = (List<Number>) b;
aPlain.addAll(bPlain);
So, do I really have to either give up captures in the declaration (the capture came to me from an interface, so I'll have to change some API), or stick with type casts with suppression annotations (which generally suck and complicates code a bit)?
You have essentially two lists of possibly different types. Because ? extends Number means a class which extends Number. So for list a it can be classA and for list b it can be for example classB. They are not compatible, they can be totally different.
The problem is that if you use List<? extends Number> you could actually do:
List<? extends Number> a = new ArrayList<Integer>();
List<? extends Number> b = new ArrayList<Double>();
a.addAll(b); //ouch, would add Doubles to an Integer list
The compiler can't tell from List<? extends Number> what the actual type parameter is and thus won't let you do the add operation.
You also shouldn't cast the lists to List<Number> if you get them as a parameter, since you could actually have a list of Integer objects and add Double objects to it.
In that case you better create a new List<Number> and add the objects from both lists:
List<Number> c = new ArrayList<Number>(a.size() + b.size());
c.addAll(a);
c.addAll(b);
Edit: in case you create both lists locally, you would not neet the ? wildcard anyway (since you'd always have List<Number>).
PECS (producer-extends, consumer-super)
You cannot put anything into a type declared with an EXTENDS wildcard
except for the value null, which belongs to every reference type
You cannot get anything out from a type declared with an SUPER
wildcard except for a value of type Object, which is a super type
of every reference type
Don't use the ? wildcard. It means "Some specific type I don't know", and since you don't know the type, you can't add anything to the list. Change your code to use List<Number> and everything will work.
This is fast becoming the most frequently asked Java question, in a hundred variations...
The thing is:
List<? extends Number> a = new ArrayList<Number>();
List<? extends Number> b = new ArrayList<Number>();
could also be read as:
List<x extends Number> a = new ArrayList<Number>();
List<y extends Number> b = new ArrayList<Number>();
How should the compiler know that x and y are the same?

Categories

Resources