如第28条所述,参数化类型是 不可变的(invariant)。对两个不同类型T1和T2而言,List<T1>与List<T2>没有父子类型关系。
1、Extends
有时候,需要的灵活性要比不可变类型所能提供的更多。考虑第26条中的堆栈下面就是他的公共API:
public class Stack<E> {
public Stack();
public void push(E e);
public E pop();
public boolean isEmpty();
}
假设增加一个方法,按顺序将一系列的元素放到堆栈中:
// pushAll method without wildcard type - deficient;
public void pushAll(Iterable<E> src) {
for (E e: src)
push(e);
}
如果尝试这样做:
Stack<Number> s = new Stack<Number>();
Iterable<Integer> i = ...;
s.pushAll(integers);
从逻辑上讲,这样应该是允许的,因为Integer是Number的子类,应当允许将Integer放到类型为Number的堆栈中。但实际运行的时候会提示Iterable<Number>与Iterable<Integer>不兼容。原因在于Iterable<Integer>并不是Iterable<Number>的子类型(参数化类型是不可变的)。
Java提供了一种特殊的参数化类型,称为有限制的通配符类型来处理类似的情况。使用有限制的通配符Iterable<? extends E>即可解决这个问题(注意,确定了子类型后,第一个类型便都是自身的子类型),修改后的程序如下:
// Wildcard type for parameter that serves as an E producer
public void pushAll(Iterable<? extends E> src) {
for (E e: src)
push(e);
}
修改之后,不仅Stack可以正确无误地编译,没有通过初始的pushAll声明进行编译的客户端代码也一样可以。因为Stack及其客户端正无误的进行了编译,你就知道一切都是类型安全的了。
2、Super
对应的,假如我们要编译一个popAll方法,初次尝试如下:
// popAll method without wildcart type - deficient;
public void popAll(Collection<E> dst) {
while(!isEmpty())
dst.add(pop());
}
如果目标集合的元素类型与堆栈完全匹配,这段代码编译时还是会正确无误的。但是,假设你有一个Stack<Number>和类型Object变量,如果从堆栈中弹出一个元素,并将它保存在该变量中,它的编译和运行都不会出错,考虑如下代码 :
Stack<Number> numberStack = new Stack<Number>();
Collection<Object> objects = ...;
numberStack.popAll(objects);
试着用上述的popAll版本编译这段代码,就会得到一个类似于第一次pushAll时的错误:Collection<Object>不是Collection<Number>的子类型。
对于这种情况,java同样提供了一种对应的有限制通配符来解决,popAll的输入参数类型不应该为“E的集合”,而应该为“E的某种超类的集合”。通配符:Collection<? super E>,根据这种方法修改后的代码如下:
public void popAll(Collection<? super E> dst) {
while(!isEmpty())
dst.add(pop());
}
3、总结
由上面这两种情况可以看出,有限制的通配符类型放宽了检查的类型,为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符类型。如果某个输入参数既是生产者,又是消费者,那么通配符类型就没有什么好处了,因为需要的是严格的类型匹配,这是不用任何通配符而得到的。
下面的助记符便于让你记住要使用哪种通配符类型类型:
PESC表示producter-extends, consumer-super。
如果参数化类型表示一个T生产者,就使用<? extends T>;如果它表示一个T消费者,就使用<? super T>。
在我们的Stack实例中,pushAll的src参数产生E实例供Stack使用,因此src相应的类型为Iterable<? extends E>;popAll的dst参数通过Stack消费E实例,因此dst的相应类型为Collection<? super E>。PECS这个助记符突出了使用通配符类型的基本原则。Naftalin和wadler称之为Get and Put Principle。
还要记住所有的comparable和comparator都是消费者。