【问题标题】:Java Generic QuestionJava 通用问题
【发布时间】:2011-08-23 20:42:19
【问题描述】:

以下代码可以编译,但如果我取消注释注释行,它不会,我很困惑为什么。 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>());
    }
}

【问题讨论】:

    标签: java generics inheritance types


    【解决方案1】:

    第一个代码是不安全的——想象一下你实际上是这样写的:

    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!
    

    这显然是好的。编译器必须以同样的方式处理这两者,所以它使它们都无效。

    【讨论】:

    • 嗨,乔恩。你可以解释吗 ?为什么不安全?在我看来,橙色的例子应该可以工作。它应该在水果列表中添加一个橙子。
    • 最重要的是,如果我们想要保存一个通用的水果列表,解决方案是什么?
    • @user450602:但它实际上不是一个水果列表——它是一个List&lt;Apple&gt;。由于类型擦除,Java 无法分辨,但这就是它的创建方式。我将编辑该示例以显示更多为什么它不应该工作。
    • @user450602:这取决于您所说的“通用水果列表”是什么意思——如果您的意思是一个可以添加任何水果的列表,那么您稍后才知道里面有什么水果,你只需使用List&lt;Fruit&gt; list = new ArrayList&lt;Fruit&gt;();
    • @user450602:在你不需要需要的时候。例如,如果您将List&lt;? extends Fruit&gt; 传递给一个方法,它可以获取 任何值并知道它是Fruit,这可能就是它所需要的。如果你传入List&lt;? super Fruit&gt;,那么它知道它可以添加任何水果,但不知道它可以从列表中得到什么类型。
    【解决方案2】:

    除了 Jon 的出色回答之外,另请参阅 question on PECS,它涵盖了很多相同的领域。

    【讨论】:

      【解决方案3】:

      如果您想要真正的技术性解释,我建议您通读component based software 中的这些幻灯片,以全面了解该问题,因为您的小问题会在大规模上产生巨大影响 :)

      真正的术语是逆变协方差。在 these slides 中搜索有关 OO 设计及其在大型系统中的限制的信息。

      你的问题是这些混合了一些泛型 :)

      即,您的合同(某种比水果更具体的水果)指定列表必须能够保留所有种水果,但是您创建了一个列表,只能容纳某种水果(苹果或比苹果更具体的东西),据我说这应该会引发编译器错误,但是 Java 编译器太好了,而且generics 没有在 Java 中正确实现(从语义上,从内省的角度来看)。

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

      一个具体的例子:

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

      一个一般的例子:

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

      ElementOfList 必须同时满足协变和逆变,因为对象既可以放入(协变)也可以检索(逆变),并且只留下不变性,即相同的类型/类。

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

      【讨论】:

        猜你喜欢
        • 2012-05-03
        • 1970-01-01
        • 2015-08-09
        • 2016-09-19
        • 1970-01-01
        • 2011-01-30
        • 2018-02-13
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多