How come one must use the generic type Map<?, ? extends List<?>>
instead of a simpler Map<?, List<?>>
for the following test()
method?
public static void main(String[] args) {
Map<Integer, List<String>> mappy =
new HashMap<Integer, List<String>>();
test(mappy);
}
public static void test(Map<?, ? extends List<?>> m) {}
// Doesn't compile
// public static void test(Map<?, List<?>> m) {}
Noting that the following works, and that the three methods have the same erased type anyways.
public static <E> void test(Map<?, List<E>> m) {}
This is because the subclassing rules for generics are slightly different from what you may expect. In particular if you have:
then
List<B>
is not a subclass ofList<A>
It's explained in details here and the usage of the wildcard (the "?" character) is explained here.
Fundamentally,
List<List<?>>
andList<? extends List<?>>
have distinct type arguments.It's actually the case that one is a subtype of the other, but first let's learn more about what they mean individually.
Understanding semantic differences
Generally speaking, the wildcard
?
represents some "missing information". It means "there was a type argument here once, but we don't know what it is anymore". And because we don't know what it is, restrictions are imposed on how we can use anything that refers to that particular type argument.For the moment, let's simplify the example by using
List
instead ofMap
.A
List<List<?>>
holds any kind of List with any type argument. So i.e.:We can put any
List
intheAnyList
, but by doing so we have lost knowledge of their elements.When we use
? extends
, theList
holds some specific subtype of List, but we don't know what it is anymore. So i.e.:It's no longer safe to add anything to the
theNotSureList
, because we don't know the actual type of its elements. (Was it originally aList<LinkedList<Float>>
? Or aList<Vector<Float>>
? We don't know.)We can put these together and have a
List<? extends List<?>>
. We don't know what type ofList
it has in it anymore, and we don't know the element type of thoseList
s either. So i.e.:We've lost information both about
theReallyNotSureList
, as well as the element type of theList
s inside it.(But you may note that we can assign any kind of List holding Lists to it...)
So to break it down:
The
Map
works the same way, it just has more type parameters:Why
? extends
is necessaryYou may know that "concrete" generic types have invariance, that is,
List<Dog>
is not a subtype ofList<Animal>
even ifclass Dog extends Animal
. Instead, the wildcard is how we have covariance, that is,List<Dog>
is a subtype ofList<? extends Animal>
.So applying these ideas to a nested
List
:List<String>
is a subtype ofList<?>
butList<List<String>>
is not a subtype ofList<List<?>>
. As shown before, this prevents us from compromising type safety by adding wrong elements to theList
.List<List<String>>
is a subtype ofList<? extends List<?>>
, because the bounded wildcard allows covariance. That is,? extends
allows the fact thatList<String>
is a subtype ofList<?>
to be considered.List<? extends List<?>>
is in fact a shared supertype:In review
Map<Integer, List<String>>
accepts onlyList<String>
as a value.Map<?, List<?>>
accepts anyList
as a value.Map<Integer, List<String>>
andMap<?, List<?>>
are distinct types which have separate semantics.Map<?, ? extends List<?>>
is a shared supertype which imposes safe restrictions:How the generic method works
By using a type parameter on the method, we can assert that
List
has some concrete type.This particular declaration requires that all
List
s in theMap
have the same element type. We don't know what that type actually is, but we can use it in an abstract manner. This allows us to perform "blind" operations.For example, this kind of declaration might be useful for some kind of accumulation:
We can't call
put
onm
because we don't know what its key type is anymore. However, we can manipulate its values because we understand they are allList
with the same element type.Just for kicks
Another option which the question does not discuss is to have both a bounded wildcard and a generic type for the
List
:We would be able to call it with something like a
Map<Integer, ArrayList<String>>
. This is the most permissive declaration, if we only cared about the type ofE
.We can also use bounds to nest type parameters:
This is both permissive about what we can pass to it, as well as permissive about how we can manipulate
m
and everything in it.See also
? extends
and? super
.