Marco Zielbauer
Marco Zielbauer

Reputation: 525

Generic Wildcards with Map

class SuperCl  {}
class A extends SuperCl {}
class B extends SuperCl {}

static void method(Map<Integer, List<? extends SuperCl>> map) {}

public static void main(String[] args) {
    method(new HashMap<Integer, List<A>>()); //ERROR
}

The compile time error is that the types are incompatible: Map<Integer, List<A>> cannot be converted to Map<Integer, List<? extends SuperCl>>

How can I fix it and where does the error come from? I assume it comes from the "method" being static.

EDIT: I changed the map implementation to HashMap (copy error) - this should not change anything

Upvotes: 0

Views: 242

Answers (4)

newacct
newacct

Reputation: 122439

Generics are invariant. For parameterized types to be compatible, their type arguments must match exactly, unless one of them is a wildcard at the top level. Map<Integer, List<A>> is not a subtype of Map<Integer, List<? extends SuperCl>> because List<A> is not identical to List<? extends SuperCl>. Yes, List<A> is a subtype of List<? extends SuperCl>, but they are not identical, which is what is needed.

As you may know, List<Dog> is not a subtype of List<Animal>, even though Dog is a subtype of Animal. It's the same situation here. A subtype relationship of the type arguments does not lead to a subtype relationship of the parameterized types (that would be called "covariant"; Java array types are covariant, but generics are not).

One solution to this is to use a wildcard at the top level. For example, List<Dog> is a subtype of List<? extends Animal>. Similarly in your case, Map<Integer, List<A>> is a subtype of Map<Integer, ? extends List<? extends SuperCl>>. So you can declare your method as:

static void method(Map<Integer, ? extends List<? extends SuperCl>> map) {}

Upvotes: 0

poshjosh
poshjosh

Reputation: 321

This works:

    class SuperCl  {}
    class A extends SuperCl {}
    class B extends SuperCl {}

    static <T extends SuperCl> void method(Map<Integer, List<T>> map) {}

    public static void main(String[] args) {
        method(new HashMap<Integer, List<A>>()); //NO MORE ERROR
    }

I simply moved the generics:

<T extends SuperCl>

to the static method declaration. This makes it verifiable at compile time. On the other hand, having that generic at the method argument is not compile time verifiable.

Upvotes: 0

Andy Turner
Andy Turner

Reputation: 140318

A HashMap<Integer, List<A>> isn't a Map<Integer, List<? extends SuperCl>>, because you can add any type of List<? extends SuperCl> to the latter.

For example:

Map<Integer, List<A>> original = new HashMap<Integer, List<A>>();
// Raw types to intentionally break the type system.
Map<Integer, List<? extends SuperCl>> map = (Map) original;

List<B> listOfB = new ArrayList<>();
listOfB.add(new B());
map.put(0, listOfB);

List<A> listOfA = original.values().iterator().next();
A item = listOfA.get(0);  // ClassCastException.

If you could do that, you'd have been able to add a value that's not a List<A> to it. Hence it's not allowed.


You could change the type in the method signature to this, for example:

Map<Integer, ? extends List<? extends SuperCl>>

and that would be fine, because you can't put any value into that (other than literal null).

Upvotes: 3

Smutje
Smutje

Reputation: 18133

Change your method to

static <T extends SuperCl> void method(Map<Integer, List<T>> map) {
}

Edit: The error mainly comes from the use of a nested generic. If you would have something like

static void method (List<? extends SuperC1> list) {

}

public static void main (String[] args) {
  List<A> list = new ArrayList<>();

  method(list);
}

you would not get a compile time error because A satisfies ? extends SuperCl.

Upvotes: 3

Related Questions