Java 泛型:无法将 List<SubClass> 转换为 List<SuperClass>?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3246137/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java Generics: Cannot cast List<SubClass> to List<SuperClass>?
提问by SiLent SoNG
Just come across with this problem:
刚遇到这个问题:
List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1; // compile error: incompatible type
Where the type DataNode is a subtype of Tree.
其中 DataNode 类型是 Tree 的子类型。
public class DataNode implements Tree
To my surprise, this works for array:
令我惊讶的是,这适用于数组:
DataNode[] a2 = new DataNode[0];
Tree[] b2 = a2; // this is okay
This likes a bit strange. Can anyone give an explanation on this?
这喜欢有点奇怪。任何人都可以对此做出解释吗?
采纳答案by Jon Skeet
What you're seeing in the second case is array covariance. It's a bad thing IMO, which makes assignments within the array unsafe - they can fail at execution time, despite being fine at compile time.
您在第二种情况下看到的是array covariance。IMO 是一件坏事,它使数组中的赋值变得不安全——它们可能在执行时失败,尽管在编译时没问题。
In the first case, imagine that the code didcompile, and was followed by:
在第一种情况下,假设代码确实编译了,然后是:
b1.add(new SomeOtherTree());
DataNode node = a1.get(0);
What would you expect to happen?
你希望发生什么?
You can do this:
你可以这样做:
List<DataNode> a1 = new ArrayList<DataNode>();
List<? extends Tree> b1 = a1;
... because then you can only fetch things from b1
, and they're guaranteed to be compatible with Tree
. You can't call b1.add(...)
precisely because the compiler won't know whether it's safe or not.
...因为那样你只能从 获取东西b1
,并且它们保证与Tree
. 您不能b1.add(...)
精确调用,因为编译器不知道它是否安全。
Have a look at this section of Angelika Langer's Java Generics FAQfor more information.
有关更多信息,请查看Angelika Langer 的 Java 泛型常见问题解答的这一部分。
回答by Julian Aubourg
Well, I'll be honest here: lazy genericity implementation.
好吧,我在这里说实话:懒惰的通用性实现。
There's no semantic reason not to allow your first affectation.
没有语义上的理由不允许你第一次做作。
Incidentally, though I adored templating in C++, generics, together with the kind of silly limitation we have here, are the main reason why I gave up on Java.
顺便说一句,尽管我喜欢 C++ 中的模板,但泛型以及我们在这里遇到的那种愚蠢的限制是我放弃 Java 的主要原因。
回答by BobTurbo
DataNode might be a subtype of Tree, but List DataNode is not a subtype of List Tree.
DataNode 可能是 Tree 的子类型,但 List DataNode 不是 List Tree 的子类型。
https://docs.oracle.com/javase/tutorial/extra/generics/subtype.html
https://docs.oracle.com/javase/tutorial/extra/generics/subtype.html
回答by Andrei Fierbinteanu
List<DataNode>
does not extend List<Tree>
even though DataNode
extends Tree
. That's because after your code you could do b1.add(SomeTreeThatsNotADataNode), and that would be a problem since then a1 would have an element that is not a DataNode in it as well.
List<DataNode>
List<Tree>
即使DataNode
extends也不会扩展Tree
。那是因为在你的代码之后你可以做 b1.add(SomeTreeThatsNotADataNode),这将是一个问题,因为那时 a1 也会有一个不是 DataNode 的元素。
You need to use wildcard to achieve something like this
你需要使用通配符来实现这样的事情
List<DataNode> a1 = new ArrayList<DataNode>();
List<? extends Tree> b1 = a1;
b1.add(new Tree()); // compiler error, instead of runtime error
On the other hand DataNode[]
DOES extend Tree[]
. At the time it seemed like the logical thing to do, but you can do something like:
另一方面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
This is why when they added generics to Collections they chose to do it a little differently to prevent runtime errors.
这就是为什么当他们将泛型添加到集合时,他们选择稍微不同的方式来防止运行时错误。
回答by Draco Ater
It is the answer from C#, but I think it doesn't actually matter here, as the reason is the same.
这是来自 C# 的答案,但我认为这里实际上并不重要,因为原因是相同的。
"In particular, unlike array types, constructed reference types do not exhibit “covariant” conversions. This means that a type List<B> has no conversion (either implicit or explicit) to List<A> even if B is derived from A. Likewise, no conversion exists from List<B> to List<object>.
“特别是,与数组类型不同,构造引用类型不表现出“协变”转换。这意味着类型 List<B> 没有转换(隐式或显式)到 List<A>,即使 B 是从 A 派生的。同样,不存在从 List<B> 到 List<object> 的转换。
The rationale for this is simple: if a conversion to List<A> is permitted, then apparently one can store values of type A into the list. But this would break the invariant that every object in a list of type List<B> is always a value of type B, or else unexpected failures may occur when assigning into collection classes."
这样做的理由很简单:如果允许转换为 List<A>,那么显然可以将 A 类型的值存储到列表中。但这会打破 List<B> 类型列表中的每个对象始终是 B 类型值的不变性,否则在分配到集合类时可能会发生意外失败。”
http://social.msdn.microsoft.com/forums/en-US/clr/thread/22e262ed-c3f8-40ed-baf3-2cbcc54a216e
http://social.msdn.microsoft.com/forums/en-US/clr/thread/22e262ed-c3f8-40ed-baf3-2cbcc54a216e
回答by sepp2k
When arrays were designed (i.e. pretty much when java was designed) the developers decided that variance would be useful, so they allowed it. However this decision was often criticized because it allows you to do this (assume that NotADataNode
is another subclass of Tree
):
当设计数组时(即几乎在设计 java 时),开发人员认为方差会很有用,所以他们允许这样做。然而,这个决定经常受到批评,因为它允许你这样做(假设它NotADataNode
是 的另一个子类Tree
):
DataNode[] a2 = new DataNode[1];
Tree[] b2 = a2; // this is okay
b2[0] = new NotADataNode(); //compiles fine, causes runtime error
So when generics were designed it was decided, that generic data structures should only allow explicit variance. I.e. you can't do List<Tree> b1 = a1;
, but you can do List<? extends Tree> b1 = a1;
.
因此,在设计泛型时就决定,泛型数据结构应该只允许显式变化。即你不能做List<Tree> b1 = a1;
,但你可以做List<? extends Tree> b1 = a1;
。
However if you do the latter, trying to use the add
or set
method (or any other method which takes a T
as an argument) will cause a compile error. This way it is not possible to make the equivalent of the above array problem compile (without unsafe casts).
但是,如果您选择后者,尝试使用add
orset
方法(或任何其他将 aT
作为参数的方法)将导致编译错误。这样就不可能编译上述数组问题的等价物(没有不安全的强制转换)。
回答by Daniel Martin
The short explanation: it was a mistake to allow it originally for Arrays.
简短的解释:最初允许它用于数组是错误的。
The longer explanation:
更长的解释:
Suppose this were allowed:
假设这是允许的:
List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1; // pretend this is allowed
Then couldn't I proceed to:
然后我不能继续:
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!!
}
Now an ideal solution would allow the kind of cast you want when using a variant of List
that was read-only, but would disallow it when using an interface (like List
) that's read-write. Java doesn't allow that kind of variance notation on generics parameters, (*) but even if it did you wouldn't be able to cast a List<A>
to a List<B>
unless A
and B
were identical.
现在,理想的解决方案将在使用List
只读变体时允许您想要的类型转换,但在使用List
读写接口(如)时不允许它。Java 不允许在泛型参数 (*) 上使用这种差异表示法,但即使是这样,您也无法将List<A>
aList<B>
强制转换为 a除非A
并且B
是相同的。
(*) That is, doesn't allow it when writing classes. You can declare your variable to have the type List<? extends Tree>
, and that's fine.
(*) 也就是说,在编写类时不允许这样做。您可以将变量声明为具有 type List<? extends Tree>
,这很好。
回答by tjin
Short answer: List a1 is not the same type as List b2; In a1 you can put any objecttype wichs extens DataNode. So it may contain other types than Tree.
简短回答:列表 a1 与列表 b2 的类型不同;在 a1 中,您可以放置任何扩展 DataNode 的对象类型。所以它可能包含除 Tree 之外的其他类型。
回答by lindelof
This is a classic problem with generics implemented with type erasure.
这是使用类型擦除实现的泛型的经典问题。
Suppose that your first example really did work. You would then be able to do the following:
假设您的第一个示例确实有效。然后,您将能够执行以下操作:
List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = a1; // suppose this works
b1.add(new Tree());
But since b1
and a1
refer to the same object, it means that a1
now refers to a List
that holds both DataNode
s and Tree
s. If you try to get that last element, you will get an exception (can't remember which one).
但是由于b1
anda1
指的是同一个对象,这意味着a1
现在指的List
是同时包含DataNode
s 和Tree
s 的 a。如果您尝试获取最后一个元素,则会出现异常(不记得是哪个)。
回答by Matthew Wise
If you do have to cast from List<DataNode>
to List<Tree>
, and you know it is safe to do so, then an ugly way to achieve this is to do a double-cast:
如果您确实必须从List<DataNode>
to 转换List<Tree>
,并且您知道这样做是安全的,那么实现这一点的丑陋方法是进行双重转换:
List<DataNode> a1 = new ArrayList<DataNode>();
List<DataNode> a1 = new ArrayList<DataNode>();
List<Tree> b1 = (List<Tree>) (List<? extends Tree>) a1;
List<Tree> b1 = (List<Tree>) (List<? extends Tree>) a1;