Java Generics:无法将List <SubClass>强制转换为List <SuperClass>?

SiL*_*oNG 104 java generics

刚遇到这个问题:

List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1;  // compile error: incompatible type
Run Code Online (Sandbox Code Playgroud)

DataNode类型是Tree的子类型.

public class DataNode implements Tree
Run Code Online (Sandbox Code Playgroud)

令我惊讶的是,这适用于数组:

DataNode[] a2 = new DataNode[0];
Tree[] b2 = a2;   // this is okay
Run Code Online (Sandbox Code Playgroud)

这有点奇怪.谁能对此作出解释?

Jon*_*eet 116

你在第二种情况下看到的是数组协方差.IMO是一件坏事,它使得数组内的赋值不安全 - 它们可能在执行时失败,尽管在编译时很好.

在第一种情况下,想象代码确实编译,然后是:

b1.add(new SomeOtherTree());
DataNode node = a1.get(0);
Run Code Online (Sandbox Code Playgroud)

你期望发生什么?

你可以这样做:

List<DataNode> a1 = new ArrayList<DataNode>();
List<? extends Tree> b1 = a1;
Run Code Online (Sandbox Code Playgroud)

...因为那时你只能从中获取东西b1,并且它们保证与之兼容Tree.您无法b1.add(...)准确调用,因为编译器不会知道它是否安全.

有关更多信息,请查看Angelika Langer的Java Generics FAQ的这一部分.

  • 啊..!这工作:列表<?扩展树> (14认同)

Mat*_*ise 21

如果你必须从投List<DataNode>List<Tree>了,你知道它是安全的话,那么丑陋的方式实现这一目标是做一个双投:

List<DataNode> a1 = new ArrayList<DataNode>();

List<Tree> b1 = (List<Tree>) (List<? extends Tree>) a1;


Dan*_*tin 18

简短的解释:最初允许它用于阵列是一个错误.

更长的解释:

假设这是允许的:

List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1;  // pretend this is allowed
Run Code Online (Sandbox Code Playgroud)

然后我不能继续:

b1.add(new TreeThatIsntADataNode()); // Hey, b1 is a List<Tree>, so this is fine

for (DataNode dn : a1) {
  // Uh-oh!  There's stuff in a1 that isn't a DataNode!!
}
Run Code Online (Sandbox Code Playgroud)

现在一个理想的解决方案是允许在使用List只读变量时所需的类型转换,但在使用List读写接口(例如)时会禁止它.Java不允许在泛型参数上使用这种方差表示法,(*)但是即使它确实如此,你也无法将a转换List<A>为a,List<B>除非A并且B相同.

(*)也就是说,在写课时不允许这样做.你可以声明你的变量有类型List<? extends Tree>,那没关系.


And*_*anu 9

List<DataNode>List<Tree>即使延伸也没有DataNode延伸Tree.那是因为在你的代码之后你可以做b1.add(SomeTreeThatsNotADataNode),那将是一个问题,因为那时a1将有一个不是DataNode的元素.

您需要使用通配符来实现这样的功能

List<DataNode> a1 = new ArrayList<DataNode>();
List<? extends Tree> b1 = a1;
b1.add(new Tree()); // compiler error, instead of runtime error
Run Code Online (Sandbox Code Playgroud)

另一方面,DataNode[]DOES延伸Tree[].当时看起来像是合乎逻辑的事情,但你可以这样做:

DataNode[] a2 = new DataNode[1];
Tree[] b2 = a2; // this is okay
b2[0] = new Tree(); // this will cause ArrayStoreException since b2 is actually a DataNode[] and can't store a Tree
Run Code Online (Sandbox Code Playgroud)

这就是为什么当他们为集合添加泛型时,他们选择以稍微不同的方式来防止运行时错误.


sep*_*p2k 7

当设计数组时(即在设计java时几乎相同),开发人员认为方差是有用的,所以他们允许它.然而,这个决定经常被批评,因为它允许你这样做(假设这NotADataNode是另一个子类Tree):

DataNode[] a2 = new DataNode[1];
Tree[] b2 = a2;   // this is okay
b2[0] = new NotADataNode(); //compiles fine, causes runtime error
Run Code Online (Sandbox Code Playgroud)

因此,当设计泛型时,通用数据结构应该只允许显式方差.即你做不到List<Tree> b1 = a1;,但你可以做到List<? extends Tree> b1 = a1;.

但是,如果您执行后者,尝试使用addor set方法(或任何其他T以参数作为参数的方法)将导致编译错误.这样就不可能使上述数组问题的编译等效(没有不安全的强制转换).