2011-05-12 91 views
3

下面的代码编译,但如果我取消注释注释行,它不,我很困惑为什么。 HashMap的确扩展了AbstractMap,并且宣告map的第一行编译得很好。Java通用问题

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>()); 
    } 
} 

回答

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! 

这显然是不OK唉。编译器必须以相同的方式处理这两者,因此它们都是无效的。

+0

嗨jon。你可以解释吗 ?为什么不安全?在我看来,橙色的例子应该工作。它应该添加一个橙色的水果名单。 – Jeb 2011-05-12 06:47:35

+0

,最重要的是如果我们想要一个通用的水果清单,解决方案是什么? – Jeb 2011-05-12 06:48:30

+0

@ user450602:但它不是*实际上*水果清单 - 这是一个列表'。由于类型擦除,Java无法分辨,但这就是它被创建的原因。我将编辑该示例以更多地展示它为什么不起作用。 – 2011-05-12 06:48:36

0

如果你想真正的技术解释,我会建议通过这些幻灯片阅读component based software对这一问题的全貌,因为你的小问题已经进行了大规模的巨大分歧:)

的实质是逆变协方差。在these slides中搜索关于面向对象设计及其在大型系统中的局限性。

你的问题是,这些与一些仿制药混合混合在:)

也就是说,你的合同(一些东西,是东西比水果更具体的水果)指定列表必须是能够保留全部种水果,但是你创建一个只能容纳某种水果的列表(苹果或比苹果更具体),根据我应该提出一个编译器错误,但是Java编译器太漂亮了,仿制药没有正确实现用Java语言(从表面上看,从内省的角度来看)。

容器实例对于容器类型/类可以是协变的或不变的,但实例的Containee必须是容器类型/类的不变量。

一个具体的例子:

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

的一般示例:

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

ElementOfList必须满足两个协方差和逆变因为对象都可以被放入(协方差)和检索(逆变)而且只留下不变性,即相同类型/类别。

这是一个很长的答案,但我希望它能帮助有人在未来提出类似的问题。