关于java:为什么只在某些地方允许将子类传递给有界通配符?

Why is passing a subclass to a bounded wildcard only allowed in certain places?

以下是来自Generics教程的:

假设R类扩展S,

1
2
3
public void addR(List<? extends S> s) {
    s.add(0, new R()); // Compile-time error!
}

您应该能够理解为什么不允许使用上面的代码。s.add()的第二个参数的类型是?扩展s——s的未知子类型。因为我们不知道它是什么类型,所以我们不知道它是否是r的超类型;它可能是或可能不是这样的超类型,所以在那里传递r是不安全的。

我已经读了几遍了,但我还是不太明白为什么下面是一个错误

给出list.add()的签名

1
void add(int index, E element)

它不等于

1
void add(int index, <? extends S> element) // just to explain the idea, not a valid syntax

为什么它是一个错误调用add(0,new r())r是一个s?


以下是斜体文本所指的内容:

List类型的参数s不仅可以是ListList的实例,而且可以是List的实例,其中T扩展s。在这种情况下,即使R也扩展了sR也不一定扩展了T(它们可能是,例如类层次中的兄弟姐妹)。由于在这样的集合中只能放置类型为T的值,因此编译器无法保证在编译时将R放置在该集合中是安全的。

为了给出一个更具体的例子,即使Double扩展了Number,也不能将Double添加到List中!这是因为类型为List的变量可以在运行时被分配一个List,并且不允许在这样的列表中添加Double

实际上,您不能实际调用声明为List的列表的add方法,因为在运行时,通配符始终可以表示s的某些子类型,而这些子类型不是您要添加的内容的超类。但是,您可以从这样的列表中读取,因为它保证通配符是s的子类型,因此可以分配给s类型的变量:

1
2
3
4
public S getElement(List<? extends S> s) {
  S result = s.get(0);
  return result;
}

这个一般概念被称为PECS(生产者扩展,消费者超级)。有效Java的第5章(方便地说,它是你可以从书的网站下载的示例性章节)有更多的关于这个和其他微妙的泛型的说法。


我想这是最简单的解释

类结构:

1
2
3
4
public class List<? extends S> {}
public class S {}
public class R extends S {}
public class T extends R {}

代码用法:

1
List<T> list = new List<T>();

在这种情况下,以下内容无效:

1
list.add(new R());