kotlin笔记: 协变和逆变

协变和逆变是kotlin的泛型语法的一部分,第一次看到这俩东西的定义时,还有点懵:使用 out 使得一个类型参数协变,协变类型参数只能用作返回值,可以作为返回值类型但是无法作为入参的类型; in 使得一个类型参数逆变,逆变类型参数只能用作函数入参,可以作为入参的类型但是无法作为返回值的类型

脑子里冒出无数个疑问:

  • 为什么协变只能用作输出,逆变只能用作输入?

  • 协变和逆变存在的意义是什么,使用他们有什么好处?

类型安全

其实协变和逆变存在的目的就是为了类型安全,我们来看两个例子就知道了:

首先是协变的例子:

1
2
3
4
5
6
7
8
9
10
11
class Box<out A>(private val content: A){
fun getContent(): A = content
}

open class Animal
class Dog: Animal()

fun main(){
val dogBox: Box<Dog> = Box(Dog())
val animalBox: Box<Animal> = dogBox // 由于协变的存在,这样是合法的
}

你可以试试如果把 Box 类的泛型参数的out关键字去掉的话 val animalBox: Box<Animal> = dogBox 这行就会报错。

我们来分析一下上述代码,前面说过,使用out关键字标记的泛型类型只能作为返回值的类型而不能作为函数的参数类型,即只能出现在return后面,不能出现在成员函数的参数列表中,Box<Animal>表示这个实例的Animal类型的值只能作为其成员函数的返回值,而Box<Dog>表示这个实例的Dog类型的值只能作为其成员函数的返回值,由于AnimalDog的父类,所以将dogBox赋值给animalBox是符合逻辑的,因为如果 dogBox的某个函数返回Dog,animalBox的一个相同的函数返回的是Animal,根据面向对象的规则,子类型可以和父类型兼容的。

理解了协变,那么就不难理解逆变了:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Box<in A>(private val content: A){
fun printA(item: A){
println("Content is : $item")
}
}

open class Animal
class Dog: Animal()

fun main(){
val animalBox: Box<Animal> = Box(Animal())
val dogBox: Box<Dog> = animalBox // 由于逆变的存在,这样是合法的
}

可以这样理解:Box<Animal> 可以接收并处理 Animal 类型的数据,所以也可以处理其子类型 Dog,因此赋值是安全的。