【问题标题】:Assigning to multilevel wildcards分配给多级通配符
【发布时间】:2014-07-14 13:28:25
【问题描述】:

简单类:

class Pair<K,V> {

}

还有一些作业:

Collection<Pair<String,Long>> c1 = new ArrayList<Pair<String,Long>>();
Collection<Pair<String,Long>> c2 = c1; // ok
Collection<Pair<String,?>> c3 = c1; // this does not compile
Collection<? extends Pair<String,?>> c4 = c1; // ok

为什么第三个不能编译,而第四个完全合法?

编译器错误:

Type mismatch: cannot convert from Collection<Pair<String,Long>> to Collection<Pair<String,?>>

【问题讨论】:

  • 编译错误是什么?
  • 考虑到Pair&lt;String,?&gt; c3 = new Pair&lt;String,Long&gt;(); 很好,这似乎很奇怪!
  • 如果你将它转换为对象,然后再转换回泛型类型也可以!
  • 是时候致电 JSL 专家了!
  • 试试Collection&lt;Pair&lt;String, ? extends Long&gt;&gt; c3 = c1;

标签: java generics bounded-wildcard


【解决方案1】:

我将尝试使用两个简单的规则来解释 Java 泛型。这些规则足以回答您的问题,并且几乎可以在任何情况下记住:

  1. 除非A = B,否则X&lt;A&gt;X&lt;B&gt; 两个泛型类型永远不可赋值。即,泛型默认情况下是不变的
  2. 通配符允许分配X&lt;A&gt;
    • X&lt;?&gt;
    • X&lt;? extends T&gt; iff A 可分配给T(递归地应用规则到AT
    • X&lt;? super T&gt; iff T 可分配给A(递归地应用规则到TA

案例c3 = c1

在您的示例中,您尝试将Collection&lt;Pair&lt;String,Long&gt;&gt; 分配给Collection&lt;Pair&lt;String,?&gt;&gt;。也就是说,在您的情况下A = Pair&lt;String,Long&gt;B = Pair&lt;String,?&gt;。由于这些类型不相等,因此它们是不可赋值的;他们违反了规则 1

问题是,为什么通配符没有帮助?答案很简单:
规则 2 是可传递的。即,X&lt;X&lt;A&gt;&gt; 不能分配给X&lt;X&lt;?&gt;&gt;,最外层必须有一个通配符;否则规则 2 不适用于最外层。

案例c4 = c1

在这里,您在外部类型中有一个通配符。因为它在外部类型中,所以规则 2 生效:A = Pair&lt;String,?&gt; 可分配给 B = ? extends Pair&lt;String,Long&gt;(同样,因为规则 2)。因此,这是合法的。

一般方法

以下是检查任何复杂泛型类型的方法:只需使用两条规则逐级检查每个泛型。从最外层开始。一旦某个级别违反了规则,您就知道该分配是非法的;如果所有级别都遵守规则,那么分配是合法的。让我们再次考虑您的类型:

X = Collection<Pair<String,Long>>
Y = Collection<Pair<String,?>>
Z = Collection<? extends Pair<String,?>> 

X 可以分配给 Y 吗?

// Outermost level:
A = Pair<String,Long>, B = Pair<String,?>
  => B is no wildcard and A != B (Rule 1), so this is illegal!

X 可以分配给 Z 吗?

// Outermost level:
A = Pair<String,Long>, B = ? extends Pair<String,?>
  => We got a wildcard, so Rule 2 states this is legal if the inner level is legal
// Inner level: (we have to check both parameters)
A = String, B = String => Equal, Rule 1 applies, fine!
A = Long, B = ? => B is wildcard, Rule 2 applies, fine!

要记住的简单规则

每个级别的通用嵌套要么需要完全相同 (A=B),要么B 需要在此级别中包含通配符。

【讨论】:

    【解决方案2】:

    首先,让我们通过删除额外的类型参数来简化代码:

    Collection<List<Long>> c1 = new ArrayList<List<Long>>();
    Collection<List<Long>> c2 = c1; // ok
    Collection<List<?>> c3 = c1; // this does not compile
    Collection<? extends List<?>> c4 = c1; // ok
    

    我们知道List&lt;? extends T&gt;本质上的意思是“一个List你可以从中得到T”,而List&lt;?&gt;List&lt;? extends Object&gt;是一回事。

    所以,上述变量的类型可以解释如下:

    • c3:“Lists 的集合,可让您从他们那里获得 Objects
    • c4:“一个集合,可以让你得到Lists,让你可以从他们那里得到Object

    特别是,这种解释会引起以下情况:

    // The following line compiles, 
    // because `ArrayList<String>` is a `List` you can get `Object`s from
    c3.add(new ArrayList<String>()); 
    
    // The following line does not compile, 
    // because type of c4 doesn't allow you to put anything into it
    c4.add(new ArrayList<String>());
    

    现在,c3 = c1 被允许了,你可以看到 c3.add(new ArrayList&lt;String&gt;()) 会破坏 c1 的类型安全:

    Collection<List<Long>> c1 = new ArrayList<List<Long>>();
    Collection<List<?>> c3 = c1;
    
    c3.add(Arrays.asList("foo")); 
    
    for (List<Long> l: c1) {
        for (Long value: l) {
            // Oops, value is not a Long!
        }
    }
    

    【讨论】:

    • c4 无效?那为什么要编译呢?
    • 在第二个代码块中,您说 //invalid,因为 c4 的类型不允许您将任何内容放入其中...但是 c4 对于编译器来说是可以的!
    • 我说的是这个块中的行,而不是原始声明。
    • 集合> c3 = c1; // 这不会编译 // 以下行编译,c3.add(new ArrayList());你要么是混乱的,要么是矛盾的。我只是指出这一点......
    【解决方案3】:

    这里要记住的关键是嵌套通配符不会捕获

    这意味着您期望顶级通配符的“正常”行为(即通配符代表一个特定的东西)不适用于嵌套通配符。相反,嵌套通配符代表 any 类型。

    以这个声明为例:

    List<?> l;
    

    这意味着l 是一个特定 类型的List。很简单。

    但是这个呢?

    Collection<List<?>> c;
    

    不是一个特定类型的Lists 中的Collection。这是Lists 中的Collection每个都是一个特定类型。

    例如,您预计会发生这样的事情:

    Collection<List<?>> c = new ArrayList<List<Long>>(); // Not valid, but pretend it is
    c.add(new ArrayList<Long>()); // Valid
    c.add(new ArrayList<Integer>()); // Invalid, because c is a Collection of Lists of Long
    

    但是考虑一下:

    List<?> l = new ArrayList<String>();
    c.add(l); // Should this compile?
    

    l 的类型与c 的类型参数完全匹配,对吧?那么即使l 不是List&lt;Long&gt;,您是否也不能将l 添加到c

    还要考虑这个:

    c.iterator().next(); // Assume there is an element to return
    

    这应该返回什么类型? iterator() 返回 Iterator&lt;E&gt;next() 返回 E,这意味着...c.iterator().next() 返回 List&lt;?&gt;。这不是您期望的List&lt;Long&gt;。这是为什么呢?

    因为嵌套通配符不捕获。这就是这里的关键区别。 List&lt;?&gt; 中的通配符不捕获单一类型“整体”。它为Collection中的每个元素捕获一个单一类型

    因此,这是完全有效的代码:

    Collection<List<?>> odd = new ArrayList<List<?>>();
    odd.add(new ArrayList<String>());
    odd.add(new ArrayList<Long>());
    List<?> l = odd.iterator().next();
            // returns the ArrayList<String>, but because odd is parameterized with
            // List<?> we can technically end up with a list of anything
    

    记住这一点,让我们看看你的例子。


    Collection<Pair<String,Long>> c1 = new ArrayList<Pair<String,Long>>();
    Collection<Pair<String,Long>> c2 = c1;
    

    直觉上没问题。类型完全匹配,因此c1 可分配给c2


    Collection<Pair<String,Long>> c1 = new ArrayList<Pair<String,Long>>();
    Collection<Pair<String,?>> c3 = c1;
    

    现在,让我们回顾一下。 Collection&lt;Pair&lt;String,?&gt;&gt; 不是 Pairs 和 Strings 的 Collection 和单个未知类型。这是CollectionPairs,每个都是一对String 和一些未知类型,它可能与集合中的另一对类型相同,也可能不同。所以这是有效的:

    // Assume an appropriate object was assigned to c3
    Pair<String, ?> p1 = new Pair<String, String>("Hello", "World");
    Pair<String, ?> p2 = new Pair<String, List<String>>("Lorem", new ArrayList<>());
    Pair<String, ?> p3 = new Pair<String, Map<String, Integer>>("Ispum", new HashMap<>());
    c3.add(p1);
    c3.add(p2);
    c3.add(p3);
    

    由于这对c3 有效,但对c1 无效,因此不允许将c1 分配给c3,因为它允许您将内容放入ArrayList&lt;Pair&lt;String, Long&gt;&gt;那不是Pair&lt;String, Long&gt;


    Collection<Pair<String,Long>> c1 = new ArrayList<Pair<String,Long>>();
    Collection<? extends Pair<String,?>> c4 = c1;
    

    现在,这有点棘手。顶层捕获一个扩展Pair&lt;String, ?&gt;特定 类型。因为通配符是特定类型的超类型(例如List&lt;?&gt;List&lt;Integer&gt; 的超类型),Pair&lt;String, Long&gt; 可以被? extends Pair&lt;String, ?&gt; 捕获,因为前者扩展了后者。因此,因为Pair&lt;String, Long&gt;? extends Pair&lt;String, ?&gt; 是赋值兼容的,所以赋值是有效的。


    您可以从这里的各种答案中看出,有多种方法可以解释嵌套通配符的行为。我想要更直观的解释,我希望我做到了。

    【讨论】:

      【解决方案4】:

      Java 泛型的优点之一是编译时的类型检查更强。所以在泛型中声明的任何东西都应该完全匹配。为了使其成为一个简单的答案,我将使用一些示例。

      初始化一个你喜欢的数字列表。

      List<Number> numbers = new ArrayList<Number>();
      

      这在技术上意味着“数字”列表可以存储任何数字或数字子类的对象。但是我们不能用子类型来初始化这个列表。通用标签&lt;&gt; 中给出的任何内容都应该与分配匹配。 (尽管 Java 7 提供了类型推断)

      List<Number> intNumbers = new ArrayList<Interger>(); // Compile Error
      List<Number> doubleNumbers = new ArrayList<Double>(); // Compile Error
      List<List<String>> list = new ArrayList<ArrayList<String>>(); // Compile Error
      

      尽管 Integer 和 Double 是 Number 的子类,但泛型会阻止这些初始化。 &lt;&gt; 中指定的通用参数应该完全匹配。

      现在,如果分配是紧密绑定的,数字列表如何存储子类对象?答案是add(), addlAll().. etc 方法接受E 或任何扩展E 的东西,其中E 是我们提供的泛型类型。因此,如果列表“数字”E 为Number,则以下语句完全有效。

      List<Integer> intNumbers = new ArrayList<Integer>();
      List<Number> numbers = new ArrayList<Number>();
      numbers.add(new Integer(1));
      numbers.add(new Double(1.0));
      numbers.addAll(intNumbers);
      

      还有一个例外是通配符。通配符用于接受任何参数。所以下面的说法是有效的。

      List<?> numbers = new ArrayList<Number>();
      Map<?, ?> unKnownMap = new HashMap<String, String>();
      
      // OR
      
      List<Integer> intNumbers = new ArrayList<Integer>();
      List<?> numbers = intNumbers;
      

      同样

      Pair<String,?> p = new Pair<String, Long>(); // Is valid.
      

      但是现在我们不能在这个列表中添加任何东西,因为元素应该扩展?,这是一个未知类型,唯一允许的元素是null,它是每个类型的成员。 Java 不会从分配中推断出这一点。所以以下导致错误

      List<?> numbers = new ArrayList<Number>(); // Works fine.
      numbers.add(new Integer(1)); // Compile Error
      numbers.add(new Double(1.0)); // Compile Error
      

      此外,通配符推断仅发生在一个级别上。任何嵌套都应该再次与通常的泛型完全匹配。所以

      List<List<?>> list = new ArrayList<List<String>>(); // Compile error because nested List<String> doesn not exactly match List<?>
      List<List<?>> list = new ArrayList<List<?>>(); // OK - Valid 
      List<List<Integer>> intList = new ArrayList<List<Integer>>();
      List<List<?>> numbers = intList; // Compile error because nested List<Integer> doesn not exactly match List<?>
      

      这就是你的情况

      Collection<Pair<String,Long>> c1 = new ArrayList<Pair<String,Long>>();
      Collection<Pair<String,Long>> c2 = c1; // ok
      Collection<Pair<String,?>> c3 = c1; // Compile error because nested Pair<String,?> doesn not exactly match Pair<String,Long>
      

      所以你可以做类似的事情

      Collection<Pair<String,?>> c3 = new ArrayList<Pair<String,?>>(c2);
      //OR
      Collection<Pair<String,?>> c3 = new ArrayList<Pair<String,?>>();
      c3.addAll(c2);
      

      案例 4:当你说 List&lt;? extends List&lt;?&gt;&gt; 时,它又是第一级。这意味着它检查有问题的元素是否扩展List&lt;?&gt;

      List<? extends List<?>> list = new ArrayList<List<String>>(); // Works similar to List<?> l = new ArrayList<String>();
      List<? extends List<List<?>>> numbers = new ArrayList<List<List<String>>>(); // Compile error - Nested level similar to List<List<String>> lst = new ArrayList<List<String>>();
      

      所以Collection&lt;Pair&lt;String,?&gt;&gt; c3 = new ArrayList&lt;Pair&lt;String, Long&gt;&gt;(); 类似于Pair&lt;String,?&gt; p = new Pair&lt;String, Long&gt;();

      【讨论】:

      • 但是为什么将 Pair 分配给 Pair 不会导致问题呢?
      猜你喜欢
      • 2018-05-11
      • 2023-03-03
      • 2017-02-21
      • 1970-01-01
      • 1970-01-01
      • 2017-04-26
      • 1970-01-01
      相关资源
      最近更新 更多