In my opinion it should work, but it does not. Why? Source code:
package javaapplication1;
import java.util.*;
class A
{
public static <K,V> Map<K,V> map()
{
return new HashMap<K,V>();
}
}
class Person {}
class Dog {}
public class JavaApplication1
{
static void f(Map<Person, List<? extends Dog>> peopleDogList) {}
public static void main(String[] args)
{
f(A.<Person, List<Dog>>map());
}
}
Very simple code. Compiler error:
method f in class JavaApplication1 cannot be applied to give types;
required: Map<Person, List<? extends Dog>
found: Map<Person, List<Dog>>
reason: actual arguments Map<Person, List<Dog>>
cannot be converted to Map<Person, List<? extends Dog>
by method invocation conversion.
Map<Person, List<? extends Dog>
is more general, so the compiler should be able to convert?
Also this: Map<Person, List<? extends Dog>> peopleDogList = A.<Person, List<Dog>>map();
does not work. ? extends Dog
means object that inherits Dog
or Dog
, so word Dog
should be ok?
Map<Person, List<Dog>>
is not compatible with Map<Person, List<? extends Dog>>
. In this case, the map's value type is expected to be List<? extends Dog>
, not something that is convertible to same. But if you used Map<Person, ? extends List<? extends Dog>>
for f
's parameter, it will work.
Here's a simple example involving more basic types:
Map<String, List<?>> foo = new HashMap<String, List<Object>>(); // error
Map<String, ? extends List<?>> foo = new HashMap<String, List<Object>>(); // ok
The OP asks why that behaviour occurs. The simple answer is that type parameters are invariant, not covariant. That is, given a type of Map<String, List<?>>
, the map's value type must be exactly List<?>
, not something similar to it. Why? Imagine if covariant types were allowed:
Map<String, List<A>> foo = new HashMap<>();
Map<String, List<?>> bar = foo; // Disallowed, but let's suppose it's allowed
List<B> baz = new ArrayList<>();
baz.add(new B());
bar.put("baz", baz);
foo.get("baz").get(0); // Oops, this is actually a B, not an A
Oops, the expectation for foo.get("baz").get(0)
to be an A
is violated.
Now, suppose we do it the correct way:
Map<String, List<A>> foo = new HashMap<>();
Map<String, ? extends List<?>> bar = foo;
List<B> baz = new ArrayList<>();
baz.add(new B());
bar.put("baz", baz); // Disallowed
There, the compiler caught the attempt to put an incompatible list into foo
(via the alias bar
). This is why the ? extends
is required.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With