joy keeps flowin'

协变和逆变

xx
目次

泛型 #

泛型的作用是能对不同的类型抽离相同的逻辑,写成模板类和方法,以此减少重复代码。

但是,我们知道在Java和Kotlin中的泛型只在编译期间生效,运行时都是Object。

在一般的使用场景中是没有问题的,像是这样:

1
2
3
4
5
6
7
8
9
class Animal{}

class Dog extends Animal{}

class Cat extends Animal{}

List<Animal> animals = new ArrayList();
animals.add(new Dog());
animals.add(new Cat());

因为类型是相同的,一切都没有问题。

Java是面向对象的语言,因此ArrayList是可以赋值给List的,那List能不能接收List和List的实例呢?

答案是不能,还是以为泛型擦除。试想一下,加入可以赋值,等到取元素的时候怎么知道是什么类型的呢?类型是Dog?有没有Cat类型的呢?这就很麻烦。

Java和Kotlin又是怎么解决的呢?就是协变和逆变。

协变 #

当像下面这样声明时就可以赋值了。

1
2
3
4
List<Dog> dogs = new ArrayList();
dogs.add(new Dog());

List<? extends Animal> animals = dogs

Dog换成Cat也是一样的,这时候是允许将Animal的子类放进去的,因为能保证放进去一定是Animal类型的。

这就是协变。

逆变 #

如果我想向动物的合集中添加Cat。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Pekingese extends Dog{}

void addDog(List<? super Dog> dogs){
  dogs.add(new Dog());
}

List<Dog> dogs = new ArrayList();
addDog(dogs);
List<Animal> animals = new ArrayList();
addDog(animals);
List<Cat> cats = new ArrayList();
// addDog(cats);编译不过
List<Pekingese> pekingeses = new ArrayList();
// addDog(cats);编译不过

只允许某一个类的超类添加进去,这就是逆变了。

标签:
Categories: