这里讨论一下Java中的泛型(Generics),重点说明协变(covariant)和逆变(contravariance)

首先,在Java中,泛型类型是不变(invariant)的,这意味这 List<String> 并不是 List<Object> 的子类。并且使用有界通配符(bounded wildcards)可以增加API的灵活性。 为了方便下面讨论,我们这里定义3个有继承关系的类: Animal, Cat, Dog

class Animal {}
class Cat extends Animal {}
class Dog extends Animal {}

协变(covariant) Link to heading

首先我们查看一下 CollectionaddAll 的方法签名:

class Collection<E> {
    boolean addAll(Collection<? extends E> c);
}

addAll 的参数类型定义成 Collection<? extends E> 而非 Collection<E> 的好处在于可以让这个方法接受类型为 EE 的子类型的Collection ,这样极大的增加了API的灵活性,这也是符合逻辑的,比如说我们定义了 Collection<Animal> 这样一个动物集合,那其实 猫集合狗集合 都是可以添加进 动物集合 的。一个很重要的点是,我们对这样一个带有上边界(extends-bound)的通配符的集合(Collection<? extends Animal>)只能读(read),不能写(write),为什么呢?我们上面说过, addAll 支持接受 猫集合狗集合 等其他动物集合,那作为一个通用方法,如果我们支持写的话,那我们写(write)什么动物呢? 写(write) 猫,如果传递进来的是狗集合那就会发生类型不匹配,所以,我们仅仅能从 Collection<? extends Animal> 知道,这个集合里的元素都是 Animal ,这是一定不会有问题的,所以对于这样类型的参数,我们只支持 读(read)

这样我们有个清晰的理解:虽然 Collection<Cat> 不是 Collection<Animal> 的子类型,但 Collection<Cat>Collection<? extends Animal> 的子类型,换句话说,带有上边界的通配符使这个类型发生了协变(the wildcard with an extends-bound (upper bound) makes the type covariant)。

void covariant(List<? extends Animal> items) {
    Animal first = items.get(0);  
}

逆变(contravariance) Link to heading

Collection<? super Dog> 这种类型匹配所有泛型类型是 Dog 及其超类的集合类型,如 Collection<Dog>Collection<Animal>Collection<Object> 。一个很重要的点是,对于发生逆变(contravariance)的类型,我们既可以对其 读(read) 也可以对其 写(write) ,但是:读(read)的类型是 Object ,而不是 Dog ,因为我们并不知具体是 DogAnimal 还是 Object ,我们仅仅知道这些类的共同点,它们有一个共同的超类- Object ;写(write)的类型只支持 Dog 类型,因为我们并不知具体是 DogAnimal 还是 Object ,我们仅仅知道这些类的共同点,它们有一个子类- Dog

void contravariance(List<? super Dog> items) {
    items.add(new Dog());
    Object first = items.get(0);  
}

PECS Link to heading

这是 Producer-Extends, Consumer-Super 的缩写,表示从生产者里读(协变(covariant)),写入到消费者(逆变(contravariance))。

Joshua Bloch recommends:For maximum flexibility, use wildcard types on input parameters that represent producers or consumers, and proposes the following mnemonic: PECS