泛型和继承?

3
public static void main(String... args) {
    List<Child> list1 = new ArrayList<Child>();
    method2(list1);
}

public static void method2(List<Parent> list1) {
}   

我遇到了以下编译错误

方法method2(List)未定义...

可以通过将List<Parent> list1修改为List<? extends Parent> list1来解决上述问题。

但是,如果我尝试添加子对象,如下所示:

public static void method2(List<? extends Parent> list1) {
    Child child1 = new Child();
    list1.add(child1);
}

它再次给出编译错误

在类型 List 中,方法 add(capture#1-of ? extends Parent)对于参数(Child)不适用

所以我的问题是,如果可以将 List<Child> 作为参数传递给 List<? extends Parent> list1,为什么我们不能在 List<? extends Parent> 下添加子对象?


请发布Child和Parent类的定义。 - Stephan
1
请查看PECS规则。你可以有一个类Child2 extends Parent。如果允许的话,你可以将一个Child实例添加到List<Child2>中。 - Alexis C.
作为一个不相关的注释,每当我看到 class Child extends Parent {} 时,我就会开始抽搐。孩子不应该成为父母!!! LSVParent parent = new Child(); 是继承的一个荒谬的类比。 - Radiodef
这是因为你在考虑子类和父类时,从生物学的角度思考,而不是从类关系的角度思考。他从未提到Child扩展了Parent,而每个人都理解了这个问题,这一点值得注意。 - Daniel Langdon
@DanielL。事实上,我经常看到父/子被特别用作生物类比(例如,请参见我在Google的第一页找到的这个教程)。此外,如果OP使用class Gidget extends Gadget {},我们也会理解问题。它本来可以是任何东西。无论如何,我并不是在批评这个问题。 - Radiodef
3个回答

6
这是一个非常普遍的误解。Child继承Parent并不意味着List扩展了List。在这种情况下听起来很不直观,但事实就是这样。根据Java教程所述:
给定两个具体类型A和B(例如,Number和Integer),MyClass与MyClass没有关系,无论A和B是否相关。 MyClass和MyClass的共同父类是Object。
详见
this
至于向列表中添加内容,简短的回答是:想象一下你有另一个class Child2 extends Parent,现在,在method2(List list1)中作为参数接收到的列表可以是List或List。因此,考虑到第二种情况是可能的,添加Child1对象是不安全的。
现在,您不能进行加法并不意味着您不能做其他有用的事情,例如获取大小等。

第二种情况也显示编译时错误“...不适用于参数(Child2)” - TSKSwamy
当然,它是完全对称的。我举了一个例子,说明为什么两者都会失败,因为你无法知道列表中实际对象的类型,而不是说它对Child2有效。 - Daniel Langdon
真的很令人困惑。看起来我必须死记硬背这个,而这并不是我喜欢的事情。 - M Sach
另一种解释是:您的方法接收一个参数“扩展父类的某些东西的列表”,而不知道“某些东西”是什么。因此,您只能执行不需要了解它的操作:列表的属性、选择元素并将其用作普通父指针等。另一方面,如果不知道应该是什么,就无法添加新的“某些东西”。 - Daniel Langdon

1
所以我的问题是,如果可以将List<Child>作为参数传递给List<? extends Parent> list1,为什么我们不能在List<? extends Parent>下添加子对象?
假设我们可以。现在假设我们有:
class Parent {}
class Mother extends Parent {}
class Father extends Parent {}

static void m(List<? extends Parent> parents) {
    parents.add(new Father());
}

List<Mother> mothers = new ArrayList<>();
m(mothers);
// throws 'ClassCastException: cannot cast Father to Mother'
Mother actuallyAFather = mothers.get(0);

一个 List<? extends Parent> 是一个列表,最多存储 Parent 或者某个我们不再了解的 Parent 子类型。我们只能添加 null 到它里面。因此,你可能需要的是:
public static void method2(List<? super Child> list1) {
//                                ^^^^^^^^^^^
    Child child1 = new Child();
    list1.add(child1);
}

List<? super Child>是一个列表,我们可以向其中添加Child。也许它是一个List<Parent>,也许它是一个List<Child>,但我们不关心。我们只关心它是一个我们可以向其中添加Child的列表。

另请参见:


0

在阅读更多内容后,这是我的答案

我们不能将 List<Dog> 分配给 List<Animal>,因为 List<Animal> 表示我们可以添加任何动物,如猫、狗等, 而 List<Dog> 表示我们只能添加狗/子类型,但不能添加猫。因此,分配者会认为他只会得到狗/子类型, 但实际上他也可能得到猫对象(因此会出现运行时错误)。

以下是示例:

psvm{
List<Dog> list1 = new ArrayList<Dog>();
list1.add(new Dog()); 
method1(list1);// compilation error
}

public static void method1(List<Animal> list1) {
list1.add(cat); // Good
list1.add(dog); // Good
}

另一个常见的误解是关于上限泛型,它恰好相反。
当我们说 List<? extends Animal> list1 时,除了 null 之外,我们不能添加任何值,但是我们可以分配任何 Animal/Sub 类型。 如果允许添加,那么它可以包含任何动物,比如猫、狗,但在检索(即向下转换)时,我们不知道实际类型。
psvm{
List<Dog> list1 = new ArrayList<Dog>();
list1.add(new Dog()); 
method1(list1);// good
}

public static void method1(List<? extends Animal> list1) {
list1.add(cat); // compilation error
list1.add(dog); // compilation error
}

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