【发布时间】:2026-02-19 09:55:02
【问题描述】:
我有一个集合类型:
Collection<A> collecA
我的对象中有一个列表:
List<B> listB
B 延伸 A
class B extends A { ... }
但我不能执行以下操作:
collecA = listB
我不明白为什么,因为 Collection 是由 List 实现的。
【问题讨论】:
标签: java generics collections
我有一个集合类型:
Collection<A> collecA
我的对象中有一个列表:
List<B> listB
B 延伸 A
class B extends A { ... }
但我不能执行以下操作:
collecA = listB
我不明白为什么,因为 Collection 是由 List 实现的。
【问题讨论】:
标签: java generics collections
让我们暂时假设您可以按照您的描述进行操作:
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 是一个集合
持有As。但是,如果允许上述分配,那么我们有一个
问题因为collecA 确实引用了List<B> 实例 - 我只是
将A 添加到只能包含Bs 的列表中!
提问者还说:
我不明白为什么,因为 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>变量只能引用可以容纳As的Lists。但是,List<B> instance 不能容纳 As。因此,像listA 这样的List<A> 变量 不能被分配对listB 引用的List<B> 实例 的引用。
或更一般地说:B 是 A 的子类不暗示 SomeGenericClass<B> 是 SomeGenericClass<A> 的子类 (JLS §4.10 :子类型不通过泛型扩展:T <: U 并不意味着C<T> <: C<U>。)
Java 泛型教程中的这个示例/类比帮助我理解了这一点:
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 = ...;
但是“动物笼子”呢?英语是模棱两可的,所以准确地说让我们假设我们正在谈论一个“all-animal cabinet”:
Cage<Animal> animalCage = ...;
这是一个笼子,旨在容纳各种动物,混合在一起。它必须有足够坚固的栏杆以容纳狮子,并且间隔足够紧密以容纳蝴蝶。
...
既然狮子是一种动物(狮子是动物的子类型),那么问题就变成了,“狮子笼子是动物笼子的一种吗?Cage<Lion> 是Cage<Animal> 的子类型吗?”。根据上述动物笼子的定义,答案一定是“否”。这很令人惊讶!但仔细想一想,这是完全有道理的:不能假设狮子笼可以关在蝴蝶里,也不能假设蝴蝶笼可以关狮子。因此,这两个笼子都不能被认为是“全动物”笼子:
animalCage = lionCage; // compile-time error
animalCage = butterflyCage; // compile-time error
"
【讨论】:
Collection<? extends A> collecA
这解决了它。问题不是List extends Collection,而是泛型类型。
【讨论】:
Java 泛型不是协变的。
更多详情请参阅Java Theory and Practice: Generics Gotchas。
该页面显示了一个简单的示例,如果它是协变的,则会破坏类型系统:
想象一下,您可以将 List
分配给 List 。然后下面的代码将允许您将不是 Integer 的内容放入 List :
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!
【讨论】:
您可以将 List 分配给 Collection,但不能将 List 分配给 Collection。
想象一下如果这可能会发生什么:
List<B> = new ArrayList<B>();
Collection<A> collecA = listB; //Assume that this line compiles
collecA.add(new A());
B item = listB.get(0); //ClassCastException!
如您所见,我们通过将具体类型 A 的实例添加到本应只有 B 类型(或后代)对象的集合中来“欺骗”泛型类型系统。 因此,对 B 执行隐式强制转换的最后一行失败并出现 ClassCastException。它出什么问题了?编译器无法保证类型安全,这违反了 Java 泛型原则之一。
因此,决定 List 是 Collection,而不是 List(或 Collection)。
作为旁注,有趣的是,数组不遵循相同的规则:String[] 是一个 Object[],并且赋值是合法的。
【讨论】: