为什么仅在某些地方允许将子类传递给有界通配符?

时间:2009-12-04 06:49:33

标签: java generics bounded-wildcard

以下内容来自泛型教程:

说R类延伸S,

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

您应该能够找出上述代码被禁止的原因。 s.add()的第二个参数的类型是?扩展S - 一个未知的S子类型。由于我们不知道它是什么类型,我们不知道它是否是R的超类型;它可能是也可能不是这样的超类型,所以在那里传递R是不安全的。

我已经阅读了几次,但我仍然不太明白为什么以下是错误

鉴于List.add()的签名

void add(int index, E element)

不等同于

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

为什么是错误调用add(0,new R())R是S?

2 个答案:

答案 0 :(得分:3)

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

s类型的参数List<? extends S>不仅可以是List<S>List<R>的实例,还可以是List<T> T } extends S。在这种情况下,即使R也扩展SR也不一定会扩展T(它们可能是,例如类层次结构中的兄弟姐妹)。由于您只能在这样的集合中放置T类型的值,编译器无法保证在编译时将R放在那里是安全的。

为了给出更具体的示例,即使Double扩展List<? extends Number>,也无法向Double添加Number!这是因为List<? extends Number> 类型的变量可以,例如,在运行时被分配List<Integer>,并且不允许向这样的列表添加Double

实际上,您实际上无法调用声明为add的列表的List<? extends S>方法,因为在运行时,通配符始终表示某个S的子类型,而不是'{1}}你要添加的东西的超类。但是,您可以从这样的列表读取,因为它保证通配符是S的子类型,因此可以分配给S类型的变量:

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

这个一般的想法被称为PECS(生产者延伸,消费者超级)。 Effective Java的第5章(很方便,这是你可以从书籍网站上下载的样本章节),对于这个以及其他微妙的泛型有更多的说法。

答案 1 :(得分:0)

想象这将是最简单的解释

班级结构:

public class List<? extends S> {}
public class S {}
public class R extends S {}
public class T extends R {}

代码用法:

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

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

list.add(new R());