本文原始发表于 https://juejin.cn/post/7077811938247311390
阅读本文你将了解:
- 什么是型变、协变、逆变和不型变
- 在 Java 和 Kotlin 中如何实现以上型变
- Java 和 Kotlin 中泛型的异同
在 Java/Kotlin 中,子类对象是可以赋值给一个父类类型的,但是父类对象不可以赋值给子类类型,例如:
// Dog 是 Animal 的子类
class Animal {
}
class Dog: Animal() {
}
val animal: Animal = dog // 把子类对象赋值给一个父类类型是可以的,dog 也是一种 Animal
val dog: Dog = animal // 把父类对象赋值给一个子类类型是不可以的,不是所有 animal 都是 dog
在引入泛型之后,情况变得更复杂:类型参数为子类的泛型类型不是类型参数为父类的泛型类型的子类,听起来很绕,看代码:
// dogs 不是 animals 的子类
val dogs: List<Dog>
val animals: List<Animal>
// dogs 和 animals 不具备任何继承关系,因此以下代码会编译报错
val animals: List<Animal> = dogs
类型参数: 泛型中尖括号中的参数称为类型参数,比如
List<String>
中的String
就是类型参数,和普通参数不同,类型参数传递的是一个类型而不是对象
为了描述方便,以下把所有「类型参数为子类的泛型」简称为「子类泛型」,「类型参数为父类的泛型」简称为「父类泛型」
对于从 Java 转到 Kotlin 的开发者们来说,要了解泛型,最好先搞懂 Java 中的泛型,再来看 Kotlin 的泛型时会变得易如反掌。
Java 中的泛型
泛型的型变(variance)
- 协变(Covariance):子类泛型是父类泛型的子类型,可以把子类泛型赋值给父类泛型
- 逆变(Contravariance):父类泛型(可以看作)是子类泛型的子类型,可以把父类泛型赋值给子类泛型
- 不型变(Invariant):子类泛型和父类泛型没有任何继关系,也不可以相互赋值
👆🏻说的是偏概念的描述,听起来特别绕,特别反人类,用代码来说人话就是(已知 Dog
是 Animal
的子类):
- 协变(Covariance):
List<Dog>
是List<Animal>
的子类型,List<Dog>
类型的对象可以赋值给List<Animal>
类型的变量 - 逆变(Contravariance):
List<Animal>
(可以看作) 是List<Dog>
的子类型,List<Animal>
类型的对象可以赋值给List<Dog>
类型的变量 - 不型变(Invariant):
List<Animal>
和List<Dog>
不具备任何继关系,也不可以相互赋值
协变、逆变本来是数学中的概念,在 Java/Kotlin 中主要应用在泛型中。
不型变
Java 中泛型是不型变的,也就是上例子上 List<Dog>
不是 List<Animals>
的子类,因此 List<Dog>
不可以赋值给 List<Animals>
,值得注意的是,Java 中数组是协变的:
List<Dog> dogs = new ArrayList<Dog>();
List<Animal> animals = dogs; // 编译报错
Dog[] dogs = new Dog[] { new Dog() };
Animal[] animals = dogs; // 编译正常
animals[0] = new Animals(); // 运行时异常
因此我们在 Java 中要优先使用泛型集合
协变
不型变性是为了保证类型安全,但带来的代价就是使得程序的灵活性降低。有时候我们希望把子类泛型对象作为实参传递给一个声明为父类泛型的形参,例如:
public int getAnimalsCount(List<Animal> animals) {
return animals.size();
}
List<Dog> dogs = new ArrayList<Dog>();
int dogsCount = getAnimalsCount(dogs); // 由于 Java 泛型的不型变,这里会编译报错的
以上,把 dogs
传递给 getAnimalsCount
方法用于计算狗狗的数量,这是一个特别合理的需求,因为不型变性导致这类需求无法实现