7

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?

Pawel
  • 1,457
  • 1
  • 11
  • 22
  • Is this issue the same reason why `List> messages2 = new ArrayList>();` compiles but `List> messages3 = new ArrayList>();` does not? – peschü Feb 05 '20 at 09:25

2 Answers2

14

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.

C. K. Young
  • 219,335
  • 46
  • 382
  • 435
  • ArrayList extends Object> alist = new ArrayList(); - why this works? It is very similar. I don't see what is the problem. – Pawel Aug 20 '13 at 23:49
  • 2
    They are similar, but not similar enough. For example, consider this: `List>` is not compatible with `List>`, but it is compatible with `List extends List>>`. When you have a `List>`, the element type of the outer list must be _exactly_ `List>`, nothing else. – C. K. Young Aug 20 '13 at 23:52
  • Now I understand;). Can you also explain more briefly why if we have collection in collection this kind of problem appears? – Pawel Aug 20 '13 at 23:59
  • @Pawel Sure, I've edited the post to put a good example of why. – C. K. Young Aug 21 '13 at 00:10
  • 1
    +1 because of your first comment - it explains it better than the answer - you should add it! – Nir Alfasi Aug 21 '13 at 00:26
  • `Map> bar` - `? extends List` means something that extends List? Could give example of proper usage of it? Allowed operation of insertion? – Pawel Aug 21 '13 at 00:52
  • @Pawel Right, so it means you could set `bar` to an instance of `Map>`, `Map>`, `Map>`, etc., since `List`, `List`, and `ArrayList>` are all assignable to (but not the same as) `List>`. Because these are all possibilities, and those types are _not_ compatible with each other, that effectively means that you cannot put any value into that map (except null). – C. K. Young Aug 21 '13 at 01:58
  • 2
    @Pawel There's an acronym we use, called PECS ([producer `extends`, consumer `super`](http://stackoverflow.com/a/2723538/13)), that helps you remember when to use `? extends Foo` and when to use `? super Foo`. Basically, if you want to _read_ `Foo` values, use `? extends Foo` (covariant). If you want to _write_ `Foo` values, use `? super Foo` (contravariant). And if you want to both read and write, then you have to use `Foo` (invariant). – C. K. Young Aug 21 '13 at 02:00
0

The problem is that you're trying to pass a HashMap<Person, List<Dog>> to a method which is expecting a Map<Person, List<? extends Dog>>.

The reason that the compiler won't let you do this is that the method you're calling might do something that's permitted on a Map<Person, List<? extends Dog>>, but not permitted on a HashMap<Person, List<Dog>>. For example, it might try to add an entry to the map, where the value is an ArrayList<Rottweiler>. This is fine for a Map<Person, List<? extends Dog>> (assuming Rottweiler extends Dog) but not for any sort of Map<Person, List<Dog>>.

Dawood ibn Kareem
  • 77,785
  • 15
  • 98
  • 110