Java Generic Question
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 A开发者_开发百科bstractMap<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>());
}
}
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.
In addition to Jon's excellent answer, see also this question on PECS which covers a lot of the same ground.
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.
精彩评论