以下代码编译,但如果我取消注释注释行,它不会,我很困惑为什么。 HashMap确实扩展了AbstractMap,并且声明map的第一行编译正常。
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>());
}
}
而且,我知道“正确的方法”是这样的:
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>());
}
}
答案 0 :(得分:7)
第一个代码是不安全的 - 想象一下你真的写了:
HashMap<String, ConcurrentHashMap<String, String>> strongMap =
new HashMap<String, ConcurrentHashMap<String, String>>();
Map<String, ? extends AbstractMap<String, String>> map = strongMap;
现在:
map.put("one", new HashMap<String, String>());
ConcurrentHashMap<String, String> x = strongMap.get("one");
我们 应该ConcurrentHashMap
- 但实际上我们只有HashMap
。
如果我们减少正在进行的泛型数量,这实际上要简单得多解释......你的场景真的等同于(比如说):
List<? extends Fruit> list = new List<Apple>();
list.add(new Apple());
看起来没问题,直到你认为它的有效性(就编译器而言)相当于:
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!
显然不没关系。编译器必须以相同的方式处理这两个,因此它们都使它们无效。
答案 1 :(得分:0)
除了Jon的优秀答案外,另请参阅此question on PECS,其中涵盖了很多相同的理由。
答案 2 :(得分:0)
如果您需要真正的技术性解释,我建议您阅读component based software中的这些幻灯片,以获得问题的完整图片,因为您的小问题在很大程度上会产生巨大的影响:)
实际条款是逆变和协方差。在these slides中搜索OO设计及其在大型系统中的局限性。
你的问题混合了一些泛型混合在一起:)
即,你的合同(某种东西比水果更具体的果实)指明列表必须能够保持所有种水果,但是你创建一个只能保存某种水果的列表(苹果或某种比苹果更具体的东西),根据我应该引发编译器错误,但是Java编译器太漂亮了泛型未在Java中正确实现(从内部和内省的角度来看)。
容器实例可以是容器类型/类的协变或不变量,但实例的容器必须是容器类型/类的不变量。
一个具体的例子:
List<Fruit> list = new ArrayList<Fruit>();
一般例子:
ConatainerType<ElementOfList> list = new MoreSpecificContainerType<ElementOfList>();
ElementOfList 必须同时满足协方差和逆变,因为对象既可以被放入(协方差)和检索(逆变),也只能保留不变性,即相同的类型/类别。
这是一个非常长的答案,但我希望它可以帮助将来提出类似问题的人。