I have a class:
class Generic<T> {
List<List<T>> getList() {
return null;
}
}
When I declare a Generic
with wildcard and call getList
method, the following assignment is illegal.
Generic<? extends Number> tt = null;
List<List<? extends Number>> list = tt.getList(); // this line gives compile error
This seems odd to me because according to the declaration of Generic
, it's natural to create a Generic<T>
and get a List<List<T>>
when call getList
.
In fact, it require me to write assignment like this:
List<? extends List<? extends Number>> list = tt.getList(); // this one is correct
I want to know why the first one is illegal and why the second one is legal.
The example I give is just some sample code to illustrate the problem, you don't have to care about their meaning.
The error message:
Incompatable types:
required :List<java.util.List<? extends java.lang.Number>>
found:List<java.util.List<capture<? extends java.lang.Number>>>
This is a tricky but interesting thing about wildcard types that you have run into! It is tricky but really logical when you understand it.
The error has to do with the fact that the wildcard
? extends Number
does not refer to one single concrete type, but to some unknown type. Thus two occurrences of? extend Number
don't necessarily refer to the same type, so the compiler can't allow the assignment.Detailed explanation
The right-hand-side in the assignment,
tt.getList()
, does not get the typeList<List<? extends Number>>
. Instead each use of it is assigned by the compiler a unique generated capture type, for exampled calledList<List<capture#1 extends Number>>
.The capture type
List<capture#1 extends Number>
is a subtype ofList<? extends Number>
, but it is not type same type! (This is to avoid mixing different unknown types together.)The type of the left-hand-side in the assignment is
List<List<? extends Number>>
. This type does not allow subtypes ofList<? extends Number>
to be the element type of the outer list, thus the return type ofgetList
can't be used as the element type.The type
List<? extends List<? extends Number>>
on the other hand does allow subtypes ofList<? extends Number>
as the element type of the outer list. So that is the right fix for the problem.Motivation
The following example code demonstrates why the assignment is illegal. Through a sequence of steps we end up with a
List<Integer>
which actually containsFloat
s!The fix that you discovered was to use the following type for
list
:This new type shifts the type error from the assignment of
list
to the call tolist.add(...)
.The above illustrates the whole point of wildcard types: To keep track of where it is safe to read and write values without mixing up types and getting unexpected
ClassCastException
s.General rule of thumb
There is a general rule of thumb for situations like this, when you have nested type arguments with wildcards:
Otherwise the inner wildcard can't "take effect", in the way you have seen.
References
The Java Tutorial contains some information about capture types.
This question has answers with general information about wildcards:
What is PECS (Producer Extends Consumer Super)?