我想知道Java无法隐式地将ArrayList<ArrayList<T>>
强制转换为Iterable<Iterable<T>>
的原因。
我的问题不在于如何明确地做到这一点。
为什么以下代码:
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>>
答案 0 :(得分:2)
ArrayList
是Iterable
,你是对的,所以ArrayList<ArrayList<Test>>
是Iterable<ArrayList<Test>>
。
但是Iterable<Iterable<Test>>
不是Iterable<ArrayList<Test>>
,因为在这种情况下,你当然不能明确地向Iterable
添加内容,但可能的情况是,你可以(和其他人一起)示例),因此您可以在其中添加Set<Test>
,这样会导致arr
。
您可以使用有界泛型来解决这个问题:
Iterable <? extends Iterable<Test>> casted = arr;
因此,当您在iterator()
上运行casted
时,您将获得Iterator<? extends Iterable<Test>>
并且每next()
个? extends Iterable<Test>
,您将获得Iterable<Test>
,即{{1}} 1}}。
您应该阅读有关covariance and countervariance in Java的内容以获取此信息。
答案 1 :(得分:2)
因为泛型如何工作。你必须使用:
ArrayList<ArrayList<Test>> arr = new ArrayList();
Iterable<? extends Iterable<Test>> casted = arr;
ArrayList<ArrayList>
与Iterable<Iterable>
不兼容,因为泛型是严格的。 ArrayList
不是Iterable
,而是? extends Iterable
但是,这有局限性。考虑
ArrayList<? extends Iterable<Test>> casted = arr;
这项工作。但是现在,casted
基本上没有类型。您无法在其上调用add
,因为没有类型兼容。
答案 2 :(得分:1)
让我们看一些类似的例子:
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
的第一个示例中,您可以看到我们可以形成的有效引用,首先概括值类型,然后生成容器的类型(&#34; spine&#34; ,当谈论序列时)。第二个示例(a2
)显示了如何从值Iterable
开始,允许您想要形成的引用类型没有问题。
您的问题可归结为valueTypeRef
为什么不属于ArrayList<Iterable<Test>>
类型,以及为什么我们需要使用上限通配符。在Java中,类型ArrayList<ArrayList<Test>>
与类型ArrayList<Iterable<Test>>
没有关系,即使ArrayList<Test>>
是Iterable<Test>
的子类型。形成valueTypeRef
会查找其隐式转换的可能关系,但不存在这样的关系,即使您作为程序员可以看到它应该存在。
Java允许您利用此子类型关系的唯一方法是使用通配符,如Wildcards and Subtyping in The Java Tutorial中所述。我们需要引入一个上限通配符(此处为ArrayList<? extends Iterable<Test>>
)来告诉类型系统我们希望新引用的值类型是其引用的值类型的子类型。 / p>
没有通配符的类型参数既不是协变也不是逆变;即使函数参数和返回类型允许沿子类型和超类型轴的正常替换,泛型类型本身(例如ArrayList<ArrayList<Test>>
和ArrayList<Iterable<Test>>
)也只能通过通配符参与这些替换。
结果类型(同样,ArrayList<? extends Iterable<Test>>
)排除了使用任何可以在列表中插入一些值的变异函数,这些值实际上是Iterable<Test>
但不是 ArrayList<Test>
。生成的引用类型是 covariant 及其指示对象,意味着它可以安全地通读,但不能安全地通读。
答案 3 :(得分:-1)
如果有人有兴趣,这就是我解决问题的方法。但这是一种解决方法,这并不能解释为什么第一个代码段不起作用:
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<ArrayList>
转换为ArrayList<LinkedList>
,因此它可以中断arr
。
由于上述原因,将ArrayList<ArrayList>
转换为Iterable<Iterable>
没有好的解决方案。但是您可以安全地(并隐式地)将ArrayList<ArrayList>
投射到Iterable<? extends Iterable>
,并像Iterable<? extends Iterable>
上一样处理Iterable<Iterable>
类型的变量。
您可以隐含地Iterable<Iterable>
是Iterable<? extends Iterable>
。