Java泛型:无法强制转换列表<;子类>;列出<;超类>;?

Java泛型:无法强制转换列表<;子类>;列出<;超类>;?,java,generics,Java,Generics,遇到这个问题: List<DataNode> a1 = new ArrayList<DataNode>(); List<Tree> b1 = a1; // compile error: incompatible type 令我惊讶的是,这适用于阵列: DataNode[] a2 = new DataNode[0]; Tree[] b2 = a2; // this is okay 这有点奇怪。有人能解释一下吗 老实说,这里是惰性泛型实现 没有语义上的理

遇到这个问题:

List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1;  // compile error: incompatible type
令我惊讶的是,这适用于阵列:

DataNode[] a2 = new DataNode[0];
Tree[] b2 = a2;   // this is okay

这有点奇怪。有人能解释一下吗

老实说,这里是惰性泛型实现

没有语义上的理由不允许你第一次做作


顺便说一下,虽然我喜欢C++中的模板,但是泛型,以及我们这里的愚蠢限制,是我放弃java的主要原因。

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

在第一种情况下,假设代码已编译,随后是:

b1.add(new SomeOtherTree());
DataNode node = a1.get(0);
你希望发生什么

您可以这样做:

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

ListDataNode可能是树的子类型,但List DataNode不是列表树的子类型

列表
不扩展
列表
,即使
数据节点
扩展
。这是因为在您的代码之后,您可以执行b1.add(sometreethatsnotatanode),这将是一个问题,因为a1中的元素也不是DataNode

您需要使用通配符来实现类似的功能

List<DataNode> a1 = new ArrayList<DataNode>();
List<? extends Tree> b1 = a1;
b1.add(new Tree()); // compiler error, instead of runtime error
这就是为什么当他们向集合中添加泛型时,他们选择了稍微不同的方式来防止运行时错误。

这是C#的答案,但我认为这实际上并不重要,因为原因是相同的

特别是,与数组类型不同,构造的引用类型不显示“协变”转换。这意味着在设计数组时(即在设计java时),类型列表没有到列表的转换(隐式或显式)开发人员认为方差是有用的,所以他们允许它。然而,这一决定经常受到批评,因为它允许您这样做(假设
notatanode
树的另一个子类
):


因此,在设计泛型时,决定泛型数据结构只允许显式差异。即,您不能执行
List b1=a1;
,但可以执行
List简短的解释:最初允许数组使用它是错误的

较长的解释是:

假设这是允许的:

List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1;  // pretend this is allowed
List a1=new ArrayList();
列表b1=a1;//假装这是允许的
那么,我不能继续:

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!!
}
b1.add(new treethastinatanode());//嘿,b1是一个列表,所以这很好
用于(数据节点dn:a1){
//啊哦!a1中有些东西不是数据节点!!
}
现在,理想的解决方案是,在使用只读的
List
变量时,允许使用您想要的类型转换,但在使用读写接口(如
List
)时,不允许使用这种类型的转换。Java不允许在泛型参数上使用这种差异表示法(*)但即使是这样,你也无法将
列表
转换为
列表
,除非
a
B
是相同的

(*)也就是说,在编写类时不允许使用它。您可以声明变量的类型为
List简短答案:
列表a1与列表b2的类型不同;

在a1中,您可以将任何objecttype放入扩展数据节点。因此,它可能包含Tree以外的其他类型。

这是使用类型擦除实现泛型的典型问题

假设您的第一个示例确实有效。然后您可以执行以下操作:

List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1;  // suppose this works
b1.add(new Tree());
List a1=new ArrayList();
列表b1=a1;//假设这样做有效
b1.添加(新树());

但是由于
b1
a1
指的是同一个对象,这意味着
a1
现在指的是一个
列表
,它同时包含
数据节点
两个元素。如果试图获取最后一个元素,您将得到一个异常(不记得是哪一个).

如果您必须从
列表
转换到
列表
,并且您知道这样做是安全的,那么实现这一点的一个丑陋的方法是进行双重转换:

List a1=new ArrayList();


List b1=(List)(List)你的解释不正确,这就是你被否决的原因(不是被我否决的,我不想麻烦).现在我们只能猜测你是因为过早放弃Java而不理解Java泛型,还是因为不理解泛型而放弃Java。如果你不回答问题,你只是陈述自己对情况的看法,那么就会投反对票。这对任何方面都没有帮助。@Julian:没有,我已经考虑过了考虑到
List
代表了一个可以添加元素的可变类型,我仍然认为这个限制是有意义的。如果你愿意,你可以称之为一个糟糕的设计决策,但一旦做出了这个决策,通过防止添加错误类型的元素来保持类型安全是非常有意义的。@JonSkeet,@BobTurbo:整个问题都是关于对象的易变性,因此我在自己的评论中谈到了它:它应该对泛型的实现方式产生如此大的影响吗?大多数时候,你需要将这种类型的强制转换交给第三方来阅读列表。不幸的事实是,问题的答案是不可变的对象,n不可变和不可读……也许集合应该是一流的公民,就像数组一样,但这完全是另一场争论,会导致运算符重载和其他像我这样“不懂泛型”的人“不懂”的“高级”主题“我当然不能理解。我坚持我的观点,Java泛型在设计和实现方面如此有限,是没有语义上的原因的。它们为了感知的安全而牺牲了表达能力,同时把它们的局限性隐藏在神秘的数学表达式后面。原因是一样的——但是J
List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1;  // pretend this is allowed
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!!
}
List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1;  // suppose this works
b1.add(new Tree());