I am trying to understand java generics and they seem extremely difficult to understand. For example, this is fine...
public class Main {
public static void main(String[] args) {
List<?> list = null;
method(list);
}
public static <T> void method(List<T> list) { }
}
... as is this...
public class Main {
public static void main(String[] args) {
List<List<?>> list = null;
method(list);
}
public static <T> void method(List<T> list) { }
}
... and this ...
public class Main {
public static void main(String[] args) {
List<List<List<?>>> list = null;
method(list);
}
public static <T> void method(List<List<T>> list) { }
}
... but this doesn't compile:
public class Main {
public static void main(String[] args) {
List<List<?>> list = null;
method(list);
}
public static <T> void method(List<List<T>> list) { }
}
Can someone explain what is going on in simple language?
The main thing to understand with generic types, is that they aren't covariant.
So whilst you can do this:
final String string = "string";
final Object object = string;
The following will not compile:
final List<String> strings = ...
final List<Object> objects = strings;
This is to avoid the situations where you circumvent the generic types:
final List<String> strings = ...
final List<Object> objects = strings;
objects.add(1);
final String string = strings.get(0); <-- oops
So, going through your examples one by one
1
Your generic method takes a List<T>
, you pass in a List<?>
; which is (essentially) a List<Object>
. T
can be assigned to the Object
type and the compiler is happy.
2
Your generic method is the same, you pass in a List<List<?>>
. T
can be assigned to the List<?>
type and the compiler is again happy.
3
This is basically the same as 2 with another level of nesting. T
is still the List<?>
type.
4
Here is where it goes a little pear shaped, and where my point from above comes in.
Your generic method takes a List<List<T>>
. You pass in a List<List<?>>
. Now, as generic types are not covariant, List<?>
cannot be assigned to a List<T>
.
The actual compiler error (Java 8) is:
required: java.util.List<java.util.List<T>>
found:
java.util.List<java.util.List<?>>
reason: cannot infer
type-variable(s) T
(argument mismatch; java.util.List<java.util.List<?>>
cannot be converted to java.util.List<java.util.List<T>>
)
Basically the compiler is telling you that it cannot find a T
to assign because of having to infer the type of the List<T>
nested in the outer list.
Lets look at this in a little more detail:
List<?>
is a List
of some unknown type - it could be a List<Integer>
or a List<String>
; we can get
from it as Object
, but we cannot add
. Because otherwise we run into the covariance issue I mentioned.
List<List<?>>
is a List
of List
of some unknown type - it could be a List<List<Integer>>
or a List<List<String>>
. In case 1 it was possible to assign T
to Object
and just not allow add
operations on wildcard list. In case 4 this cannot be done - primarily because there is not a generics construct to prevent add
to the outer List
.
If the compiler were to assign T
to Object
in the second case then something like the following would be possible:
final List<List<Integer>> list = ...
final List<List<?>> wildcard = list;
wildcard.add(Arrays.asList("oops"));
So, due to covariance, it is not possible to assign a List<List<Integer>>
to any other generic List
safely.