协变和逆变
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);编译不过
|
只允许某一个类的超类添加进去,这就是逆变了。