如28项所述,参数化类型是不可变的(invariant)。换句话说,对于任何两个截然不同的类型Type1和Type2而言,List既不是List的子类型,也不是它的超类型。虽然List不是List的子类型,这与直觉相悖,但是实际上很有意义。你可以将任何对象放进一个List中,却只能将字符串放进List中。因为List不能完成List所能做的所有事情,因此它不是子类型(由Liskov替换主体,第10项)。
有时候,我们需要的灵活性要比不可变类型所能提供的更多,考虑第29项中的Stack类,下面是它的公共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);
}
这个方法编译时正确无误,但是并非尽如人意。如果Iterable src的元素类型与堆栈的完全匹配,就没有问题。但是假如有一个Stack,并且调用了push(intVal),这里的intVal就是Integer类型。这是可以的,因为Integer是Number的一个子类型。因此从逻辑上来说,下面这个方法应该也可以:
Stack<Number> numberStack = new Stack<>();
Iterable<Integer> integers = ... ;
numberStack.pushAll(integers);
但是,如果尝试这么做,就会得到下面的错误信息,因为如前所述,参数化类型是不可变的:
StackTest.java:7: error: incompatible types: Iterable<Integer>
cannot be converted to Iterable<Number>
numberStack.pushAll(integers);
^
幸运的是,有一种解决办法。Java提供了一种特殊的参数化类型,称作有限制的通配符类型(bounded wildcard type ),来处理类似的情况。pushAll的输入参数类型不应该为“E的Iterable接口”,而应该为“E的某个子类型的Iterable接口”,有一个通配符类型证符合此意:Iterable<? Extends E>。(使用关键字extends有些误导:回忆以下第29项中的说法,确定子类型(subtype)后,每个类型便都是自身的子类型,即便它没有将自身扩展。)我们修改以下pushAll来使用这个类型:
// Wildcard type for a parameter that serves as an E producer
public void pushAll(Iterable<? extends E> src) {
for (E e : src)