英文:
Why we can't cast List<A> to List<B>
问题
List aList = new ArrayList<>();
List bList = (List) aList; // 不能成功编译
List bList = (List)(Object) aList; // 可以成功编译
无论 List 还是 List 实际上都是 Object[],为什么编译器阻止我们将 List 强制转换为 List?
感谢大家的答复。
- 如果 List 是 ArrayList,则它使用 Object[] 来保存项目。当获取项目时,它使用
(T)items[index]
将对象强制转换为 T,Java 泛型确保它将成功运行。 List<B> bList = (List<B>) aList
很危险,就像将 Object 强制转换为 String 一样。但是即使它能够工作,编译器也阻止我们这样做。- 既然编译器阻止我们这样做,为什么允许我们
List<B> bList = (List<B>)(Object) aList;
?
英文:
List<A> aList = new ArrayList<>();
List<B> bList = (List<B>) aList; //it can't compile successfully
List<B> bList = (List<B>)(Object) aList; //it's ok
both List<A>
and List<B>
actually are Object[], why compiler stop us to cast List<A>
to List<B>
=======
Thanks everyone's answer.
- If a List is ArrayList, then it uses Object[] to save items. When we get item, it use
(T)items[index]
to cast object to T, and java generic ensure it will run successfully. List<B> bList = (List<B>) aList
is dangerous, so compiler should warn us, such as Object cast to String. But compiler stop us do this, even if it can work.- Now that compiler stop us doing this, why allow us
List<B> bList = (List<B>)(Object) aList;
答案1
得分: 6
编译器理解由于A
和B
是两种不同的类型,A
的列表不能是B
的列表。自行车的列表不能是人的列表,反之亦然。因此强制转换是被禁止的。
每个列表都是一个对象。因此,您总是可以将列表转换为Object
。一旦您获得了一个对象,编译器只知道它是一个对象,因此当您说它实际上是B
的列表时,编译器只知道这可能是真的,因此它信任您并允许转换。接下来,您可能会将人放入自行车列表中,或者为自己制造其他麻烦。这不再是编译器的问题。
您可以将列表转换为Object
,但您也不能将List<A>
转换为List<Object>
。为什么呢?因为这将允许您将任何类型的对象放入本应仅容纳A
对象的列表中。请参阅底部链接的相关问题。
> 如果我们可以确保aList
中的每个项目都是B
,我认为应该可以。
不,不行。在转换后,您仍然可以使用aList.add(something)
来添加一个不是B
的项目,从而违反了bList
是B
列表的声明。如何在这种情况下将您的aList
转换为List<B>
,请参见链接的问题,其中的答案对此进行了解释,特别是fracz的答案。
> 在我看来,Java的设计者可以允许我们将列表转换为列表,但是他们没有...
当然,您可以对语言设计持有不同意见,而且您肯定不会是唯一一个持这种观点的人。
> ... A a = (A) b
,如果b
不是A
,它将抛出异常。并且我们可以找到导致这个的对象。如果List<A> aList = (List<A> bList)
可以工作,每个项目的异常都将导致列表转换错误,而我们甚至不知道哪个项目抛出异常...
这里有一个基本的区别。如果一个对象是A
,它将永远是A
。在Java中,它永远不可能成为除A
以外的任何其他类型。因此,一旦您成功地进行了转换,您可以放心地认为该类型不会对您产生进一步的不愉快惊喜。另一方面,列表,即使它当前只包含A
对象,将来可能会包含B
、C
或D
对象,甚至所有这些对象以及更多其他对象。因此,如果Java允许转换,您将在将来面临各种负面的惊喜。我认为这就是他们选择禁止的原因。我并不是说他们做出了完美的决策,只是试图解释至少有一些原因在其中。
> ... 因此Java设计者不允许我们这样做。这是我目前的理解...
您的理解是正确的。
如果您想将一个当前只包含F
对象的列表转换为List<F>
,我建议您将所有元素复制到一个新的List<F>
中(例如新的ArrayList<F>
)。没有什么可以阻止您这样做,而且Java可以帮助您控制列表副本始终只包含F
对象。
链接: 相关问题:How do you cast a List of supertypes to a List of subtypes? 我个人在您的情况下最喜欢的答案是fracz的答案。
英文:
The compiler understands that since A
and B
are two different types, a list of A
cannot be a list of B
. A list of bicycles cannot be a list of persons or vice versa. Therefore the cast is forbidden.
Every list is an object. So you can always cast a list to Object
. And once you’ve got an object, all the compiler knows is that it’s an object, so when you say it’s really a list of B
, all the compiler knows is that this might be true, so it trusts you and allows the cast. Next thing you may be putting persons into your bicycle list or making other trouble for yourself. This is no longer the compiler’s problem.
You may cast a list to Object
, but you cannot cast a List<A>
to List<Object>
either. Why not? Because this would allow you to put any kinds of objects into a list that was supposed to hold only A
objects. See the related question linked to at the bottom.
> If we can ensure each item in aList
is a B
, I think it should work.
No, it won’t. After the cast you would still be able to use aList.add(something)
to add something that is not a B
and thus violating the claim that bList
is a list of B
. How to convert your aList
to List<B>
in this case, see the linked question, the answers there do explain it, in particular the answer by fracz.
> In my opinion, Java Designer can allow us to cast list to list, but
> they don't. …
You are free to disagree about the language design, of course, and you certainly won’t be alone.
> … A a = (A) b
, if b
is not an A
, it will throw an Exception. And
> we can find which Object results this. if List<A> aList = (List<A>
can work, each item's Exception will result List cast error,
> bList)
> and we even don't know which item throw Exception. …
There’s a fundamental difference here. If an object is an A
, it will always be an A
. There is no way in Java that it could ever become anything else than an A
. So once you have successfully made the cast, you can rest assured that the type holds no further unpleasant surprises for you. A list, on the other hand, even if it currently holds only A
objects, it may at some time in the future hold B
, C
or D
objects or all of them and more. So if java allowed the cast, you would risk all kinds of negative surprises further down the road. I think that this is the reason why they chose to forbid it. I am not saying that they made the perfect decision, just trying to explain that at least there is some reason behind it.
> … So Java Designer don't allow us to do this. This is my current
> understanding, …
Your understanding is correct.
If you want to convert a list that currently holds, say, only F
objects, to a List<F>
, I suggest that you copy all of the lements into a new List<F>
(for example a new ArrayList<F>
. There’s no stopping you from doing that, and Java helps you control that the list copy will always hold F
objects only.
Link: Related question: How do you cast a List of supertypes to a List of subtypes? My own favourite answer for your situation is the one by fracz.
答案2
得分: 1
不,两个 List 都不是对象数组。它们包含一个包含类 A 或 B 的对象数组,必须指定这些类,并且它们可能不兼容。
如果你的类不兼容,你的第二行代码只会在编译时起作用,而不会在运行时起作用。
但是你也可以定义 B 继承自 A,然后你的转换应该会起作用。
英文:
Nope, both List are not Object arrays. They contain an array containing objects of class A or B which have to be specified and which are probably incompatible.
If your classes are incompatible, your second line will only work for the compiler but not on runtime.
But you could also define that B extends A, then your cast should work.
答案3
得分: 1
Java中的泛型是不变的。Java不允许我们执行上述的操作,因为这可能会导致运行时异常。例如:
List<String> listOfStrings = new ArrayList<String>();
//我们都知道String是Object的子类型,但是编译器禁止下面的代码,即使这行代码看起来是正确的。
List<Object> listOfObjects = (List<Object>) listOfStrings;
//原因是...如果Java编译器编译上面的代码,可能会出现运行时异常
listOfObjects.add(new Integer(6));
String str = listOfStrings.get(0); //运行时异常出现在这里 ---》我们期望一个String,但是索引0处的元素实际上是一个Integer对象。
我希望上面的代码足够清楚地说明为什么不能强制转换你的列表。
英文:
Generic type in Java is invariant. Java doesn't allow us to do things like above because it can lead to run time exceptions. For example:
List<String> listOfStrings = new ArrayList<String>();
//we all know that String is sub-type of Object, but Compiler prohibits below line of code even though the line looks ok.
List<Object> listOfObjects = (List<Object>) listOfStrings;
//The reason is... If Java Compiler compiles the line above, run-time exceptions could appears
listOfObjects.add(new Integer(6));
String str = listOfStrings.get(0); //Run-time exception goes here ---> we expect a String, but the item at index 0 is actually an Integer object.
I hope the code above is clear enough to demonstrate why you can not cast your lists.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论