使用限定通配符来增加 API 的灵活性
参数化类型是不变的。换句话说,对于任何两个不同类型的 Type1 和
Type2 , List
不是 List
public class Stack{ public Stack(); public void push(E e); public E pop(); public boolean isEmpty(); }
假设我们想要添加一个方法来获取一系列元素,并将它们全部推送到栈上。 以下是第一种尝试:
public void pushAll(Iterablesrc) { for (E e : src) push(e); }
这种方法可以干净地编译,但不完全令人满意。 如果可遍历的 src 元素类型与栈的元素类型完
全匹配,那么它工作正常。 但是,假设有一个 Stack
intVal 的类型是 Integer 。 这是因为 Integer 是 Number 的子类型。 从逻辑上看,这似乎也
应该起作用:
StacknumberStack = new Stack<>(); Iterable integers = ... ; numberStack.pushAll(integers);
但是,如果你尝试了,会得到这个错误消息,因为参数化类型是不变的:
StackTest.java:7: error: incompatible types: Iterable<Integer>
cannot be converted to Iterable<Number>
numberStack.pushAll(integers);
幸运的是,有对应的解决方法。 该语言提供了一种特殊的参数化类型来调用一个限定通配符类型来
处理这种情况。 pushAll 的输入参数的类型不应该是「 E 的 Iterable 接口」,而应该是「 E
的某个子类型的 Iterable 接口」,并且有一个通配符类型,这意味着: Iterable<? extends
E> 。 (关键字 extends 的使用有点误导:回忆条目 29 中,子类型被定义为每个类型都是它自己的
子类型,即使它本身没有继承。)让我们修改 pushAll 来使用这个类型
public void pushAll(Iterable<? extends E> src) { for (E e : src) push(e); }
有了这个改变, Stack 类不仅可以干净地编译,而且客户端代码也不会用原始的 pushAll 声明编
译。 因为 Stack 和它的客户端干净地编译,你知道一切都是类型安全的。
这个结论很清楚。 为了获得最大的灵活性,对代表生产者或消费者的输入参数使用通配符类型。
如果一个输入参数既是一个生产者又是一个消费者,那么通配符类型对你没有好处:你需要一个精确的
类型匹配,这就是没有任何通配符的情况
这里有一个助记符来帮助你记住使用哪种通配符类型: PECS 代表: producer-extends,consumersuper。
换句话说,如果一个参数化类型代表一个 T 生产者,使用 <? extends T> ;如果它代表 T
消费者,则使用 <? super T>