【问题标题】:Why is the implicit cast of ArrayList<ArrayList<T>> to Iterable<Iterable<T>> impossible?为什么 ArrayList<ArrayList<T>> 到 Iterable<Iterable<T>> 的隐式转换是不可能的?
【发布时间】:2014-10-10 15:59:07
【问题描述】:

我想知道Java 不能将ArrayList&lt;ArrayList&lt;T&gt;&gt; 隐式转换为Iterable&lt;Iterable&lt;T&gt;&gt; 的原因。 我的问题不在于如何明确地做到这一点。

为什么会出现以下代码:

import java.util.Iterator;
import java.util.ArrayList;

public class Test {
  public Test() {
    ArrayList<ArrayList<Test>> arr = new ArrayList();
    Iterable<Iterable<Test>> casted = arr;
  }
}

在编译期间引发以下错误?

Test.java:7: error: incompatible types: ArrayList<ArrayList<Test>> cannot be converted to Iterable<Iterable<Test>>

【问题讨论】:

  • 因为你可以做casted.add(new LinkedList());,或者添加任何其他实现Iterable的东西,即使casted应该只包含ArrayList&lt;Test&gt;的对象。
  • 不,我不能这样做 arr.add(new LinkedList()); 因为 arr 仍然是 ArrayList&lt;ArrayList&lt;Test&gt;&gt;。我也做不到casted.add(new LinkedList());,因为 Iterable 没有实现 add。
  • 好的,是的。但是如果你改为List&lt;List&lt;Test&gt;&gt; 会发生什么?那有一个add 方法。对于Iterable 的情况,语义上没有问题(因为Iterables 是只读的),但对于一般情况,它允许您违反内部模板的类型,因此Java 不允许您这样做。
  • 到目前为止,这些答案已经提到了如何解决这个问题,但他们没有解释为什么需要解决这个问题。

标签: java generics bounded-wildcard


【解决方案1】:

ArrayListIterable,你是对的,所以ArrayList&lt;ArrayList&lt;Test&gt;&gt;Iterable&lt;ArrayList&lt;Test&gt;&gt;

但是Iterable&lt;Iterable&lt;Test&gt;&gt; 不是Iterable&lt;ArrayList&lt;Test&gt;&gt;,因为在这种情况下,您当然不能显式地向Iterable 添加某些内容,但有可能情况是,您可以(通过其他示例),所以您可以例如,在其中添加Set&lt;Test&gt;,这样会破坏arr

您可以使用有界泛型解决此问题:

Iterable <? extends Iterable<Test>> casted = arr;

因此,当您在 casted 上运行 iterator() 时,您将获得 Iterator&lt;? extends Iterable&lt;Test&gt;&gt;,并且随着每个 next() 您将获得 ? extends Iterable&lt;Test&gt;,即 Iterable&lt;Test&gt;

你应该阅读一些关于 covariance and countervariance in Java 的文章来获得这个。

【讨论】:

  • @njzk2 不,我们不能,编译器会阻止它,因为它不能确定(这不是真的)? extends Iterable&lt;Test&gt; 的扩展正是 HashSet
  • 我发现您的“但是”断言令人困惑。你写道,“[我]在这种情况下,你当然不能明确地向Iterable 添加一些东西”。没有人试图这样做。也许你的意思是写,“Iterable&lt;Iterable&lt;Test&gt;&gt; 不是Iterable&lt;ArrayList&lt;Test&gt;&gt;。这样会更有意义。
  • @seh 没错,我会修复答案。
【解决方案2】:

因为泛型是如何工作的。你必须使用:

ArrayList<ArrayList<Test>> arr = new ArrayList();
Iterable<? extends Iterable<Test>> casted = arr;

ArrayList&lt;ArrayList&gt;Iterable&lt;Iterable&gt; 不兼容,因为泛型是严格的。 ArrayList 不是Iterable,而是? extends Iterable

但是,这有局限性。考虑

ArrayList<? extends Iterable<Test>> casted = arr;

这项工作。但是现在,casted 绑定基本上没有类型。你不能在上面调用add,因为没有兼容的类型。

【讨论】:

    【解决方案3】:

    我们来看几个类似的例子:

    public final class Test {
      public Test() {
        final ArrayList<ArrayList<Test>> a1 = new ArrayList<>();
        final ArrayList<? extends Iterable<Test>> valueTypeRef = a1;
        final Iterable<? extends Iterable<Test>> spinalTypeRef = valueTypeRef;
    
        final ArrayList<Iterable<Test>> a2 = new ArrayList<>();
        final Iterable<Iterable<Test>> r2 = a2;
      }
    }
    

    a1 的第一个示例中,您可以看到我们可以形成的有效引用,首先概括值类型,然后生成容器的类型(当谈到序列时,“脊椎”)。第二个示例 (a2) 展示了如何从值类型 Iterable 开始允许您轻松形成您想要的引用类型。

    您的问题归结为为什么valueTypeRef 不能是ArrayList&lt;Iterable&lt;Test&gt;&gt; 类型,以及为什么我们需要使用上限通配符。在 Java 中,类型 ArrayList&lt;ArrayList&lt;Test&gt;&gt; 与类型 ArrayList&lt;Iterable&lt;Test&gt;&gt; 没有关系,即使 ArrayList&lt;Test&gt;&gt;Iterable&lt;Test&gt; 的子类型。形成valueTypeRef 会为其隐式转换寻找这种可能的关系,但不存在这种关系,即使您作为程序员可以看到它应该存在。

    Java 允许您利用这种子类型关系的唯一方法是使用通配符,如Wildcards and Subtyping in The Java Tutorial 中所述。我们需要引入一个上限通配符(此处为ArrayList&lt;? extends Iterable&lt;Test&gt;&gt;)来告诉类型系统我们期望我们的新引用的值类型是其引用的值类型的子类型。

    没有通配符的类型参数既不是协变的也不是逆变的;即使函数参数和返回类型允许沿子类型和超类型轴进行正常替换,泛型类型本身(例如 ArrayList&lt;ArrayList&lt;Test&gt;&gt;ArrayList&lt;Iterable&lt;Test&gt;&gt;)也只能使用通配符参与这些替换。

    生成的类型(同样,ArrayList&lt;? extends Iterable&lt;Test&gt;&gt;)禁止使用任何会在列表中插入一些值的变异函数,这些值确实是Iterable&lt;Test&gt;,但不是ArrayList&lt;Test&gt;。生成的引用类型与其所指对象是协变的,大致意思是通读是安全的,但写通不安全。

    【讨论】:

      【解决方案4】:

      糟糕的解决方案

      如果有人感兴趣,这就是我解决问题的方法。但这是一种解决方法,并不能解释为什么第一个 sn-p 不起作用:

      import java.util.Iterator;
      import java.util.ArrayList;
      
      public class Test {
        public Test() {
          ArrayList<ArrayList<Test>> arr = new ArrayList();
          Iterable<Iterable<Test>> casted = (Iterable<Iterable<Test>>)(Iterable<?>)arr;
        }
      }
      

      这是不安全的操作。在这种情况下,不会造成任何伤害,因为 Iterable 是只读的,但是这种方法允许您将 ArrayList&lt;ArrayList&gt; 强制转换为 ArrayList&lt;LinkedList&gt;,因此它可以破坏 arr

      很好的解决方案

      由于上述原因,没有很好的解决方案将ArrayList&lt;ArrayList&gt; 转换为Iterable&lt;Iterable&gt;。但是您可以安全地(隐式地)将ArrayList&lt;ArrayList&gt; 转换为Iterable&lt;? extends Iterable&gt;,并像处理Iterable&lt;Iterable&gt; 一样处理Iterable&lt;? extends Iterable&gt; 类型的变量。

      您可以隐式地将Iterable&lt;Iterable&gt; 视为Iterable&lt;? extends Iterable&gt;

      【讨论】:

      • 不要这样做! 此代码通常是不安全的,因为您可以添加不是ArrayLists 的Lists,破坏arr 的类型安全,它只允许包含ArrayLists。这就像说List&lt;String&gt; l1 = null; List&lt;Object&gt; l2 = (List&lt;Object&gt;) (List&lt;?&gt;) l1; l2.add(new Object()); // l1 contains an instance of Object! 一样糟糕。
      • 糟糕,我想说的是l1 = new ArrayList&lt;String&gt;;,而不是null,但你明白了。
      猜你喜欢
      • 1970-01-01
      • 2016-06-26
      • 2013-12-06
      • 2021-09-09
      • 2020-08-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多