Everything negative-pressure,challenges-is all an opportunity for me to rise
09 September 2020
这里讨论一下Java中的泛型(Generics),重点说明协变(covariant)和逆变(contravariance)
首先,在Java中,泛型类型是不变(invariant)的,这意味这List<String>
并不是List<Object>
的子类。并且使用有界通配符(bounded wildcards)可以增加API的灵活性。
为了方便下面讨论,我们这里定义3个有继承关系的类:Animal,Cat,Dog
1
2
3
class Animal {}
class Cat extends Animal {}
class Dog extends Animal {}
首先我们查看一下Collection
的addAll
的方法签名:
1
2
3
class Collection<E> {
boolean addAll(Collection<? extends E> c);
}
将addAll
的参数类型定义成Collection<? extends E>
而非Collection<E>
的好处在于可以让这个方法接受类型为E
或E
的子类型的Collection
,这样极大的增加了API的灵活性,这也是符合逻辑的,比如说我们定义了Collection<Animal>
这样一个动物集合,那其实猫集合和狗集合都是可以添加进动物集合的。一个很重要的点是,我们对这样一个带有上边界(extends-bound)的通配符的集合(Collection<? extends Animal>)只能读(read),不能写(write),为什么呢?我们上面说过,addAll
支持接受猫集合,狗集合等其他动物集合,那作为一个通用方法,如果我们支持写的话,那我们写(write)什么动物呢?写(write)猫,如果传递进来的是狗集合那就会发生类型不匹配,所以,我们仅仅能从Collection<? extends Animal>
知道,这个集合里的元素都是Animal
,这是一定不会有问题的,所以对于这样类型的参数,我们只支持读(read)。
这样我们有个清晰的理解:虽然Collection
1
2
3
void covariant(List<? extends Animal> items) {
Animal first = items.get(0);
}
Collection<? super Dog>
,这中类型匹配所有泛型类型是Dog及其超类的集合类型,如Collection<Dog>
,Collection<Animal>
,Collection<Object>
。一个很重要的点是,对于发生逆变(contravariance)的类型,我们既可以对其读(read)也可以对其写(write),但是:读(read)的类型是Object
,而不是Dog
,因为我们并不知具体是Dog
,Animal
还是Object
,我们仅仅知道这些类的共同点,它们有一个共同的超类-Object
;写(write)的类型只支持Dog
类型,因为我们并不知具体是Dog
,Animal
还是Object
,我们仅仅知道这些类的共同点,它们有一个子类-Dog
。
1
2
3
4
void contravariance(List<? super Dog> items) {
items.add(new Dog());
Object first = items.get(0);
}
这是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
— Lenox Xian