You have a method that expects an object of type X
. In order to pass an object to this method, this object must have a type Y
that is a subtype of X
.
This may be obvious for simple cases:
void processNumber(Number number) { ... }
void callIt()
{
Integer integer = null;
processNumber(integer); // Fine. Integer is a subtype of Number
String string = null;
processNumber(string); // Error. String is NOT a subtype of Number
}
So far, so good. But now you're entering the confusing world of subtype-relationships for parameterized types. Angelika Langer goes into the details in her Generics FAQ: Which super-subtype relationships exist among instantiations of generic types?:
This is fairly complicated and the type relationships are best determined setting up tables as explained in this FAQ entry.
So I won't even try to reproduce this here, but only try to explain (simplified) why the second version works, but the first one doesn't.
The fact that the second version works can be explained with the type inference that the compiler does. Basically, it just looks at the argument, sees that it is Map<String, List<String>>
, and infers that the T
of the method declaration must be String
for this to work. You can imagine this as T
simply being replaced with String
, and then the method matches perfectly. (It's not really so easy, but can intuitively be understood like this)
The reason of why the first version does not work is seemingly simple as well. Coming back to the initial statement: The method expects an object that has a subtype of the type declared in the method signature. And the key point is:
Map<String, List<String>>
is not a subtype of Map<String, List<?>>
This fact can (in this case) even be simplified by omitting the method invocation:
Map<String, List<String>> map = new HashMap<>();
Map<String, List<?>> otherMap = map; // Does not work
Again, I'll leave the details of the "type theory" behind that to the FAQ entry linked above, but to at least explain why it is not allowed: A Map<String, List<?>>
is a map that maps strings to lists that contain an unknown/unspecified type. So the following implementation of your processMap1
method would be valid:
public void processMap1(Map<String, List<?>> map)
{
List<Number> numbers = new ArrayList<Number>();
map.put("x", numbers);
}
And imagine, calling this method with a Map<String, List<String>>
was valid:
public void f() {
Map<String, List<String>> map = new HashMap<>();
processMap1(map); // Imagine this was possible
List<String> shouldBeStrings = map.get("x");
}
then you would end up with a ClassCastException
sooner or later, because you pass Map<String, List<String>>
to the method, but the method is allowed to put a List<Number>
into this map.
Or for short: It is not allowed because it is not type-safe.
Edit in response to the comments (this goes somewhat beyond the original question, so I'll try to keep it short) :
For the second case, there is no T
to capture any type. The type is ?
, which, intuitively, can stand for any type. In the case of the suggested call from reverse
to rev
for lists, the T
is still ?
, but may be used to dedicatedly represent the type of the elements in the list. Or again, referring to the subtype relationships:
List<T>
is always a subtype of List<?>
(regardless of what T
is)
The suggested call from processMap1
to processMap2
does not work for the reasons explained above: Even when Y
is a subtype of X
, the type Map<String, Y>
is not a subtype of Map<String, X>
. Here, this question may be relevant.