【问题标题】:Java generics How to accept any derived type in generic parameterJava泛型如何接受泛型参数中的任何派生类型
【发布时间】:2016-03-09 18:17:58
【问题描述】:

在下面两行代码

HashMap<Integer, ?extends Collection<String>> map=
                                           new HashMap<Integer, TreeSet<String>>();

map.put(1,new TreeSet<String>());

第2行:HashMap类型中的put(Integer, capture#1-of ? extends Collection)方法不适用于参数(int, TreeSet)

第 1 行:这没有错误。

为什么第 1 行允许使用相同的泛型类型(TreeSet),但第 2 行不允许使用?

编辑: 使用 super 而不是 extends ,为什么以下是不允许的。

HashMap<Integer, ?super Collection<String>> map=new HashMap(<Integer, TreeSet<String>>());

但是

HashMap<Integer, ?super Collection<String>> map=new HashMap();
map.put(1,new TreeSet<String>());

允许

【问题讨论】:

    标签: java generics


    【解决方案1】:

    您收到编译器错误的原因与您无法将Dog 添加到List&lt;? extends Animal&gt; 的原因相同——当引用变量的类型具有上限通配符时,您无法使用泛型参数调用方法。 map 变量的值类型参数可以引用与? extends Collection&lt;String&gt; 匹配的任何 类型,可能是HashMap&lt;Integer, LinkedList&lt;String&gt;&gt;。你可以在调用put之前合法地插入这一行:

    map = new HashMap<Integer, LinkedList<String>>();
    

    编译器不知道映射中真正存在的确切类型,因此它必须在编译时阻止您将 putting TreeSet&lt;String&gt; 作为映射的值,其值可能类似于LinkedList&lt;String&gt;.

    对于map 中的值put(除了null),您必须删除通配符。

    HashMap<Integer, TreeSet<String>> map =
             new HashMap<Integer, TreeSet<String>>();
    

    正如 JB Nizet 所评论的,如果您删除通配符但保留 Collection,您仍然可以输入任何 Collection 的值,例如 TreeSet

    HashMap<Integer, Collection<String>> map =
             new HashMap<Integer, Collection<String>>();
    

    (另外,菱形运算符可以简化这里的声明。)

    针对添加到问题中的更改:

    在这里,您使用了下限。

    HashMap<Integer, ? super Collection<String>> map = new HashMap<Integer, TreeSet<String>>());
    

    类型参数可以是Collection&lt;String&gt; 或任何超类型,例如Object。这不允许诸如TreeSet&lt;String&gt; 之类的子类型。 Java 的泛型是不变的。允许 Collection&lt;String&gt; 的任何变化的唯一原因是通配符。

    HashMap<Integer, ? super Collection<String>> map = new HashMap<>();
    map.put(1, new TreeSet<String>());
    

    这是允许的,因为Collection&lt;String&gt; 的任何超类型都将匹配任何子类型作为参数。毕竟,TreeSet&lt;String&gt; 就是ObjectTreeSet&lt;String&gt; 可以是 put 作为 map 的值,无论是 HashMap&lt;Integer, Object&gt;HashMap&lt;Integer, Collection&lt;String&gt;&gt;,还是介于两者之间的任何类型。编译器可以证明类型安全,所以它允许调用。

    【讨论】:

    • 或者,如果地图必须接受任何类型的字符串集合,Map&lt;Integer, Collection&lt;String&gt;&gt; map = new HashMap&lt;Integer, Collection&lt;String&gt;&gt;();,可以简化为Map&lt;Integer, Collection&lt;String&gt;&gt; map = new HashMap&lt;&gt;();
    • @JBNizet 更好;我会将其添加到我的答案中。
    • 如果他将 TreeSet 转换为 Collection 他可以放入地图中会怎样
    • A Map 不是 Collection,所以new HashMap...whatever... 不匹配? extends Collection&lt;String&gt;
    • @achabahe 否。它与作为调用 put 的值传递的内容的类型无关,与通配符有关。
    【解决方案2】:

    映射的声明告诉编译器映射内部的值是一些字符串的集合。在运行时,它可能是TreeSet,但也可能是其他一些集合类型。因此编译器不允许放置TreeSet,因为它实际上可能包含ArrayList 值。

    更一般地说,每当您使用 ? 通配符使用绑定类型参数时,您实际上只能从映射中读取(例如,迭代其元素)。换句话说,你总是可以这样做:

    for(Iterator<? extends Collection<String>> iterator = map.values().iterator(); iterator.hasNext();) {
        Collection<String> collection = iterator.next();
        ... 
    }
    

    但是,在您的情况下,由于您要添加 TreeSet,这意味着您很可能知道地图 包含 TreeSet 值,因此您不需要使用通配符:

    HashMap<Integer, TreeSet<String>> map = new HashMap<>(); // In Java 7+, you can use the diamond operator when creating the HashMap
    

    【讨论】:

      【解决方案3】:

      编译器说:

      没有找到适合 put(int,TreeSet) 的方法 map.put(1, new TreeSet()); 方法 HashMap.put(Integer,CAP#1) 不适用 (实参TreeSet无法通过方法调用转换为CAP#1) 方法 AbstractMap.put(Integer,CAP#1) 不适用 (实际参数 TreeSet 不能通过方法调用转换转换为 CAP#1)其中 CAP#1 是一个新的类型变量: CAP#1 从 ? 的捕获扩展 Collection扩展集合

      我认为这是因为编译器的类型不明确,正如AR.3 所说:

      映射的声明告诉编译器里面的值 地图是一些字符串的集合。在运行时,它可能是一个 TreeSet 但它也可以是其他一些集合类型。

      如果编译器的类型确定,则代码将编译并运行没有任何问题,例如您可以编写如下代码,编译并运行成功:

      public <T extends Collection<String>> void someVoid(){
              //...
              //...
              HashMap<Integer, T > map
                      = new HashMap<>();
              map.put(1,(T) (new TreeSet<String>()));
              //...
              //...
          }
      

      【讨论】:

        【解决方案4】:

        在阅读了其他答案并对原始问题进行了更多思考(实际上是关于拥有“? extends”部分而不是没有它),我想出了以下示例,应该更容易理解。

        假设我们有这个方法:

        void doSomething(Collection<? extends Number> numbers) { ... }
        

        以及其他地方的以下用法:

        Collection<Integer> integers = asList(1, 2, 3);
        Collection<Long> longs = asList(1L, 2L, 3L);
        doSomething(integers);
        doSomething(longs);
        

        以上所有编译都很好。现在,让我们问一下,doSomething 方法可以对它接收到的集合做什么?它可以添加一些数字吗?答案是,它不能。那是因为它不安全;例如,如果它添加了一个Integer(这是一个Number),那么doSomething(longs) 调用就会失败。因此,为了防止这种可能性,编译器不允许任何尝试将任何 Number 添加到 Collection&lt;? extends Number&gt;

        同样,如果方法声明为:

        void doSomething(Collection<Number> numbers) { ... }
        

        在这种情况下,方法可以向集合中添加任何数字,但随后像 doSomething(integers) 这样的调用会成为编译器错误,因为 Integer 的集合只能 接受整数。因此,编译器再次阻止ClassCastExceptions 的可能性,确保代码是类型安全的。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-10-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-06-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多