This question already has an answer here:
这个问题已经有了答案:
- Is List<Dog> a subclass of List<Animal>? Why are Java generics not implicitly polymorphic? 15 answers
-
List
是List 的子类吗?为什么Java泛型不隐式多态?15个答案
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是树的子类型。
public class DataNode implements Tree
To my surprise, this works for array:
令我惊讶的是,这适用于array:
DataNode[] a2 = new DataNode[0];
Tree[] b2 = a2; // this is okay
This likes a bit strange. Can anyone give an explanation on this?
这有点奇怪。有人能解释一下吗?
10 个解决方案
#1
104
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.
第二种情况是数组协方差。这在IMO上是一件坏事,它使数组中的赋值变得不安全——它们在执行时可能会失败,尽管在编译时很好。
In the first case, imagine that the code did compile, 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取回东西,而且它们保证与树是兼容的。不能准确地调用b1.add(…),因为编译器不知道它是否安全。
Have a look at this section of Angelika Langer's Java Generics FAQ for more information.
请查看Angelika Langer的Java Generics FAQ部分,了解更多信息。
#2
13
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)时不允许使用这种类型的转换。Java不允许在泛型参数上使用这种差异符号,(*),但是即使它做了,您也不能将一个列表< 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.
(*)也就是说,在写课的时候是不允许的。可以将变量声明为具有类型列表 ,这没问题。
#3
13
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> a1 = new ArrayList<DataNode>();
列表
List<Tree> b1 = (List<Tree>) (List<? extends Tree>) a1;
列表
#4
9
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.
列表
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[]确实扩展了树[]。在当时,这似乎是合乎逻辑的做法,但你可以这样做:
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.
这就是为什么当他们在集合中添加泛型时,他们选择了稍微不同的方式来防止运行时错误。
#5
7
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
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 or set方法(或任何其他以T为参数的方法)将导致编译错误。这样,就不可能编译上述数组问题(没有不安全的强制类型转换)。
#6
2
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中,可以放置任何objecttype wichs扩展DataNode。所以它可能包含树以外的其他类型。
#7
1
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>.
特别是,与数组类型不同,构造引用类型不显示“协变”转换。这意味着类型列表没有转换(隐式或显式)来列出< a >,即使B来自a。同样地,也没有从List到List
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类型的值存储到列表中。但是这将打破不变量,即类型列表中的每个对象始终是类型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
#8
1
DataNode might be a subtype of Tree, but List DataNode is not a subtype of List Tree.
DataNode可能是树的子类型,但是List DataNode不是List树的子类型。
https://docs.oracle.com/javase/tutorial/extra/generics/subtype.html
https://docs.oracle.com/javase/tutorial/extra/generics/subtype.html
#9
0
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和a1指的是同一个对象,这就意味着a1现在指的是一个同时包含DataNodes和tree的列表。如果您尝试获取最后一个元素,您将得到一个异常(不记得是哪个)。
#10
-4
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的主要原因。
#1
104
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.
第二种情况是数组协方差。这在IMO上是一件坏事,它使数组中的赋值变得不安全——它们在执行时可能会失败,尽管在编译时很好。
In the first case, imagine that the code did compile, 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取回东西,而且它们保证与树是兼容的。不能准确地调用b1.add(…),因为编译器不知道它是否安全。
Have a look at this section of Angelika Langer's Java Generics FAQ for more information.
请查看Angelika Langer的Java Generics FAQ部分,了解更多信息。
#2
13
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)时不允许使用这种类型的转换。Java不允许在泛型参数上使用这种差异符号,(*),但是即使它做了,您也不能将一个列表< 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.
(*)也就是说,在写课的时候是不允许的。可以将变量声明为具有类型列表 ,这没问题。
#3
13
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> a1 = new ArrayList<DataNode>();
列表
List<Tree> b1 = (List<Tree>) (List<? extends Tree>) a1;
列表
#4
9
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.
列表
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[]确实扩展了树[]。在当时,这似乎是合乎逻辑的做法,但你可以这样做:
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.
这就是为什么当他们在集合中添加泛型时,他们选择了稍微不同的方式来防止运行时错误。
#5
7
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
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 or set方法(或任何其他以T为参数的方法)将导致编译错误。这样,就不可能编译上述数组问题(没有不安全的强制类型转换)。
#6
2
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中,可以放置任何objecttype wichs扩展DataNode。所以它可能包含树以外的其他类型。
#7
1
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>.
特别是,与数组类型不同,构造引用类型不显示“协变”转换。这意味着类型列表没有转换(隐式或显式)来列出< a >,即使B来自a。同样地,也没有从List到List
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类型的值存储到列表中。但是这将打破不变量,即类型列表中的每个对象始终是类型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
#8
1
DataNode might be a subtype of Tree, but List DataNode is not a subtype of List Tree.
DataNode可能是树的子类型,但是List DataNode不是List树的子类型。
https://docs.oracle.com/javase/tutorial/extra/generics/subtype.html
https://docs.oracle.com/javase/tutorial/extra/generics/subtype.html
#9
0
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和a1指的是同一个对象,这就意味着a1现在指的是一个同时包含DataNodes和tree的列表。如果您尝试获取最后一个元素,您将得到一个异常(不记得是哪个)。
#10
-4
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的主要原因。