3

The following code compiles but if I uncomment the commented line, it does not and I am confused why. HashMap does extend AbstractMap and the first line where map is declared compiles fine.

import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;

public class Test {

    public static void main(String args[]) {
        Map<String, ? extends AbstractMap<String, String>> map = new HashMap<String, HashMap<String, String>>();
        //map.put("one", new HashMap<String, String>());
    }
}

And, I know the "right way" is this:

import java.util.HashMap;
import java.util.Map;

public class Test {

    public static void main(String args[]) {
        Map<String, Map<String, String>> map = new HashMap<String, Map<String, String>>();
        map.put("one", new HashMap<String, String>());
    }
}
MByD
  • 135,866
  • 28
  • 264
  • 277
pathikrit
  • 32,469
  • 37
  • 142
  • 221

3 Answers3

7

The first code is unsafe - imagine you're actually written:

HashMap<String, ConcurrentHashMap<String, String>> strongMap = 
    new HashMap<String, ConcurrentHashMap<String, String>>();
Map<String, ? extends AbstractMap<String, String>> map = strongMap;

Now:

map.put("one", new HashMap<String, String>());
ConcurrentHashMap<String, String> x = strongMap.get("one");

We should have a ConcurrentHashMap - but in reality we've only got a HashMap.

This is actually a lot simpler to explain if we reduce the amount of generics going on... your scenario is really equivalent to (say):

List<? extends Fruit> list = new List<Apple>();
list.add(new Apple());

which looks okay, until you consider that it's equivalent in validity (as far as the compiler is concerned) to:

List<Apple> apples = new ArrayList<Apple>();
List<? extends Fruit> list = apples;
list.add(new Orange());
Apple apple = list.get(0); // Should be okay... but element 0 is an Orange!

which is obviously not okay. The compiler has to treat the two in the same way, so it makes both of them invalid.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Hi jon. Can you explain ? why is it not safe? It seemed to me that the orange example should work. it should add an orange to the list of fruits. – Bick May 12 '11 at 06:47
  • and most important what is the solution if we want to hold a generic list of fruits? – Bick May 12 '11 at 06:48
  • @user450602: But it's not *actually* a list of fruits - it's a `List`. Java can't tell because of type erasure, but that's what it was created as. I'll edit the example to show more of why it shouldn't work. – Jon Skeet May 12 '11 at 06:48
  • @user450602: It depends what you mean by "a generic list of fruits" - if you mean a list you can add any fruit to, and you only know later that what's in there is a fruit, you'd just use `List list = new ArrayList();`. – Jon Skeet May 12 '11 at 06:50
  • 2
    @user450602: For times when you *don't* need that. For example, if you pass a `List extends Fruit>` into a method, it can *get* any value and know it's a `Fruit`, which may be all it needs. If you pass in a `List super Fruit>` then it knows it can *add* any fruit, but doesn't know what type it could get from the list. – Jon Skeet May 12 '11 at 07:04
0

In addition to Jon's excellent answer, see also this question on PECS which covers a lot of the same ground.

Community
  • 1
  • 1
BeeOnRope
  • 60,350
  • 16
  • 207
  • 386
0

If you want the really technical explanation I would recommend reading through these slides in component based software for a complete picture of the issue since your little problem has immense ramifications on a large scale :)

The real terms are contravariance and covariance. Search for them in these slides regarding OO-design and its limitations in larger systems.

Your problem is a mix of these with some generics mixed in :)

Ie, your contract (something which is a fruit of something more specific than a fruit) specifies that the list must be able to keep all kinds of fruit, but you create a list only capable of holding a certain kind of fruit (apples or something more specific than an apple), which according to me should raise a compiler error, but the Java-compiler is too nice and generics is not properly implemented in Java (sementically, and from an introspection point-of-view).

The container instance can be covariant or invariant to the container type/class but the containee of the instance must be the invariant to the containee type/class.

A concrete example:

List<Fruit> list = new ArrayList<Fruit>();

A general example:

ConatainerType<ElementOfList> list = new MoreSpecificContainerType<ElementOfList>();

ElementOfList must fulfill both covariance and contravariance since objects can both be put into (covariance) and retrieved (contravariance) and that leaves only invariance, ie the same type/class.

This was a really long answer, but I hope it helps someone who asks a similar question in the future.

flindeberg
  • 4,887
  • 1
  • 24
  • 37