Fancy generics capture collision - java

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?

Related

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.

Wildcards in java Generics

I know Generics are invariant: for any two distinct types Type1 and Type2,
List< Type1> is neither a subtype nor a supertype of List<
Type2>
so
List<someObject> nums = new ArrayList<someObject>(); // this is correct
List<Object> nums = new ArrayList<subObject>(); // this is not correct
but
List<Number> nums = new ArrayList<Number>();
List<? super Number> sink = nums; ????(line 2)
let say if wildcard is Object so line 2 wil be
List<Object> sink = List<Number> nums
It seems that the invariant rule not applied here
can anyone explain to me why line 2 is compiled without error ?
Thank so much
If I am not wrong, you wish to have an explanation on how is the below valid code:
List<Number> nums = new ArrayList<Number>();
List<? super Number> sink = nums;
Invariance is property of the class on how its type parameter affects its subtyping.
Generics are invariant, but wild cards exist to help us with with sub-typing. They are not very useful as they dont representing anytype but represent a good hack. Below is valid
List<Animal> <: List<?>
List<?> <: List
Better example:
List<? super Animal> d1= new ArrayList<Animal>();
d1.add(new Animal());
d1.add(new Dog());
The above works, because d1 is of type List<? super Animal>. You can imagine add function to behave like:
boolean add(? super Animal e);
So to add method you can pass any variable that is subtype of ? super Animal. And
Animal <: ? super Animal
Dog <: ? super Animal
So adding a Dog or Animal works. So d1 acts as a list that can take any parameter of type Animal or subtype if it.
Similarly, you can also have below. But technically you cant add anything to this list. If there existed sometype in java that is subtype of every type, then you could properly add element of that type to it. Nothing Else.
ArrayList<? extends Animal> d1 = new ArrayList<Animal>();
Look at this answer for more info.
The invariant rule does indeed apply. The statement:
List<? super Number> sink = nums;
just doesn't work they way you are thinking.
I expect that you think that you can assign an object of type ArrayList to a variable of type List<? super Number> because the former is a subclass of the latter. This would break the invariant rule, but this is not what is happening.
The List<? super Number> variable represents the set of all possible List where someClass is Number or is an ancestor of Number.
let say if wildcard is Object so line 2 wil be
`List<Object> sink = List<Number> nums `
In this context the ? doesn't get set arbitrarily, so this doesn't happen.
Lets have:
List<Integer> li = new ArrayList<>();
List<Object> lo = new ArrayList<>();
l = lo;//not allowed
lo.add("string");
Integer i = li.get(0);//that is string there
That is why you cannot make such assignment. I think compiler doesn't treat direct assignment of new ArrayList<Integer>() other then existing variable li.

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

Java generics list

I know following is true.
List<? extends Number> aNumberSuperList = new ArrayList<>();
List<? extends Integer> aIntegerSuperList = new ArrayList<>();
aNumberSuperList = aIntegerSuperList;
But what type of objects can be added to such a list.
List<? extends Number> aNumberSuperList2 = new ArrayList<>();
aNumberSuperList2.add(???)
Only null can be added to a List<? extends Number>. This is because the exact type parameter isn't known. A List<Integer> could be assigned to this list, and so could a List<AtomicLong>. The compiler must prevent a call to add with anything but null, to avoid the situation where a Integer might be added to something that's referred to as a List<? extends Number>, but is really a List<Double>, for example. null is the only safe thing to add here, because it can be of any type.

Java Generic List<List<? extends Number>>

How come in java we cannot do:
List<List<? extends Number>> aList = new ArrayList<List<Number>>();
Even though this is OK:
List<? extends Number> aList = new ArrayList<Number>();
Compiler error message is:
Type mismatch: cannot convert from ArrayList<List<Number>> to List<List<? extends Number>>
In Java, if Car is a derived class of Vehicle, then we can treat all Cars as Vehicles; a Car is a Vehicle. However, a List of Cars is not also a List of Vehicles. We say that List<Car> is not covariant with List<Vehicle>.
Java requires you to explicitly tell it when you would like to use covariance and contravariance with wildcards, represented by the ? token. Take a look at where your problem happens:
List<List<? extends Number>> l = new ArrayList<List<Number>>();
// ---------------- ------
//
// "? extends Number" matched by "Number". Success!
The inner List<? extends Number> works because Number does indeed extend Number, so it matches "? extends Number". So far, so good. What's next?
List<List<? extends Number>> l = new ArrayList<List<Number>>();
// ---------------------- ------------
//
// "List<? extends Number>" not matched by "List<Number>". These are
// different types and covariance is not specified with a wildcard.
// Failure.
However, the combined inner type parameter List<? extends Number> is not matched by List<Number>; the types must be exactly identical. Another wildcard will tell Java that this combined type should also be covariant:
List<? extends List<? extends Number>> l = new ArrayList<List<Number>>();
I'm not very familiar with Java syntax but it seems that your issue is this:
Covariance & Contravariance
You should definitely use the ? type wildcard when appropriate, do not avoid it as a general rule. For example:
public void doThingWithList(List<List<? extends Number>> list);
allows you to pass a List<Integer> or a List<Long>.
public void doThingWithList(List<List<Number>> list);
allows you to only pass arguments declared as List<Number>. A small distinction, yes, but using the wildcard is powerful and safe. Contrary to how it may seem, a List<Integer> is not a subclass, or is not assignable, from List<Number>. Nor is List<Integer> a subclass of List<? extends Number, which is why the code above does not compile.
Your statement does not compile because List<? extends Number> is not the same type as List<Number>. The former is a supertype of the latter.
Have you tried this? Here I'm expressing that the List is covariant in its type argument, so it will accept any subtype of List<? extends Number> (which includes List<Number>).
List<? extends List<? extends Number>> aList = new ArrayList<List<Number>>();
Or even this. Here the type parameter for the ArrayList on the right-hand side is the same as the type parameter on the left-hand side, so variance is not an issue.
List<List<? extends Number>> aList = new ArrayList<List<? extends Number>>();
You should be able to just say
List<List<Number>> aList = new ArrayList<List<Number>>();
I tend to avoid the ? type wildcard whenever possible. I find that the expense incurred in type annotation is not worth the benefit.
List<List<? extends Number>> aList = new ArrayList<List<? extends Number>>();
aList.add(new ArrayList<Integer>());

Categories

Resources