如何在Java中将继承对象的列表转换为对象集合?

13

我有一个集合类型:

Collection<A> collecA

我在对象中有一个列表:

List<B> listB

B 在扩展 A

class B extends A { ... }

但我无法执行以下操作:

collecA = listB

我无法理解为什么 Collection 由 List 实现。

4个回答

19

假设您能够做到您所描述的事情:

class B extends A { ... }

Collection<A> collecA;
List<B> listB;

collecA = listB;  // normally an error, but lets pretend its allowed

collecA.add(new A()); // PROBLEM!

方法调用collecA.add(new A())看起来没有问题,因为collecA是一个持有A的集合。然而,如果允许上面的赋值,我们就会遇到一个问题,因为collecA实际上是一个指向List<B>实例的引用——我刚刚把一个A添加到了一个只能容纳B的列表中! Asker 还说:

我不明白为什么 Collection 是 List 的实现。

即使使用两个列表,这个赋值也是非法的,并不重要的是Collection是List的超类。
class B extends A { ... }
List<A> listA;
List<B> listB;
listA = listB;  // still an error, still leads to the same problem

关键是List<A> 变量只能引用可以容纳AList。然而,List<B> 实例不能容纳A。因此,像listA这样的List<A> 变量不能被分配一个由listB引用的List<B> 实例

或者更一般地说:BA的子类并不意味着SomeGenericClass<B>SomeGenericClass<A>的子类JLS §4.10子类型不会扩展泛型类型:T <: U并不意味着C<T> <: C<U>


正是来自Java Generics Tutorial的这个例子/比喻帮助我理解了这个问题:

http://java.sun.com/docs/books/tutorial/java/generics/subtyping.html

“如果你想象一些有形的物体——你可以真正看到的东西——比如一个笼子,那么理解为什么会变得更容易:

// A cage is a collection of things, with bars to keep them in.
interface Cage<E> extends Collection<E>;
...
Cage<Lion> lionCage = ...;
Cage<Butterfly> butterflyCage = ...;

那么“动物笼子”呢?英语有歧义,因此为了更准确,我们假设我们谈论的是一个“全动物笼子”

Cage<Animal> animalCage = ...;

这是一个设计用来容纳各种动物的笼子,混合在一起。它必须有足够强大的栏杆来固定住狮子,并且栏杆之间要足够接近以保持蝴蝶不逃跑。
...
由于狮子是动物的一种(狮子是动物的子类型),那么问题就变成了:“狮子笼是动物笼的一种吗?Cage<Lion>Cage<Animal>的子类型吗?”。通过上述动物笼的定义,答案必须是“否”。这很令人惊讶!但是如果你仔细想想,这是完全合理的:狮子笼不能保证能把蝴蝶关在里面,蝴蝶笼也不能保证能把狮子关在里面。因此,两种笼子都不能被认为是“全动物”笼子:
animalCage = lionCage;  // compile-time error
animalCage = butterflyCage; // compile-time error

"


3
好的,易于理解的BERT解释。在尝试理解泛型和继承时,我一直欣赏愚蠢的比喻。Kartoch应该看一下这个。 - tgai
2
@Tamon - 很高兴你(以及其他人)发现它很容易理解。我曾经也遇到过理解泛型和继承(更不用说通配符了)的问题,所以需要像Java泛型教程中那样的新颖例子来深入我的脑海。我搜索了“java generic cast animal cage”才想起我在哪里读到这个类比。 :-) - Bert F

10
Collection<? extends A> collecA

这样修复了。问题不在于List extends Collection,而是泛型类型。


5

Java泛型不是协变的

请参见Java理论与实践:泛型陷阱以了解更多细节。

该页面展示了一个简单的例子,如果它是协变的,将会破坏类型系统:

想象一下,你可以将List<Integer>分配给List<Number>。那么以下代码将允许你将非Integer类型的内容放入List<Integer>中:

List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415)); // ERROR: Adds a float to list, which is a list of Integers!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接