理解 Kotlin 的协变和逆变
协变和逆变
协变和逆变的本质是在处理”类型替换的安全性保证”这个问题
这是类型系统用来保证类型安全的机制
- 本质上是在处理”什么样的类型替换是安全的”这个问题
- 协变和逆变提供了明确的规则来判断类型替换的安全性
协变 (out)
协变(out)通过限制类型参数只能用在输出位置,从而保证了类型安全
- 协变关注的是”类型替换的安全性”
- 协变保证了”子类型可以替换父类型”的里氏替换原则
- 安全性来自于”子类型包含父类型所有行为”的保证,如果需要 Animal 的地方,提供一个 Cat 是安全的(因为 Cat 一定具备 Animal 的所有特性)
- 这种保证是单向的:子类型可以安全地替换父类型,但反过来不行
// 安全的场景(协变)
val animals: Producer<Animal> = CatProducer() // ✅ 安全
// 因为需要动物的地方,给一只猫一定可以满足需求
// 不安全的场景
val cats: Producer<Cat> = AnimalProducer() // ❌ 不安全
// 因为需要猫的地方,给一个动物不能保证它一定是猫(反过来不行)
- 当类型参数用在输入位置时,允许子类型替换父类型是不安全的,因为实现可能无法处理父类型的所有可能情况
interface AnimalShelter<T> {
fun get() = T
fun acceptAnimal(animal: T) // 收容动物
}
val catShelter: AnimalShelter<Cat> = CatShelter() //专门处理猫
// 如果允许 catShelter 当作 AnimalShelter<Animal> 使用
// 那么别人可能会试图让猫舍收容狗,这显然是不对的
fun processcatShelter(animalShelter: AnimalShelter<Animal>) {
val cat = animalShelter.get()
val dog = Dog()
animalShelter.acceptAnimal(animal) // 如果这里使用的是Cat就是安全的,但是如果是其他就不安全,所以会报错!
//❌错误点:调用者认为可以传入任何 Animal,但实际实现传入的参数只能处理 Cat,这导致了类型安全的破坏
//如果能够确保运行时类型安全 可以在使用out修饰后用 @UnsafeVariance 注解,建议只是用来做只读操作比如:比较
}
processcatShelter(catShelter) //提供Cat做处理
- 当类型参数只用在输出位置时,子类型替换父类型是安全的,因为子类型的对象一定满足父类型的要求
interface AnimalProvider<out T> {
fun get(): T // 只提供动物
}
val catProvider: AnimalProvider<Cat> = CatProvider()
// 允许 catProvider 当作 AnimalProvider<Animal> 使用
// 因为猫舍提供的猫一定是动物,这是安全的
逆变(in)
逆变(in)通过限制类型参数只能用在输入位置,从而保证了类型安全
- 逆变关注的是”处理能力的包含关系”
- 逆变保证了”更一般的处理器可以替换更具体的处理器”,因为子类型必定包含父类型的所有行为,如果需要处理 Cat 的代码,用处理 Animal 的代码也是安全的(因为处理 Animal 的代码不会用到 Cat 特有的特性)
- 安全性来自于”能处理父类型就一定能处理子类型”
- 这种保证也是单向的:处理父类型的代码可以安全地替换处理子类型的代码,但反过来不行
// 安全的场景(逆变)
val catHandler: Consumer<Cat> = AnimalHandler() // ✅ 安全
// 因为能处理所有动物的代码,一定能处理猫
// 不安全的场景
val animalHandler: Consumer<Animal> = CatHandler() // ❌ 不安全
// 因为只能处理猫的代码,不一定能处理其他动物(反过来不行)
- 当类型参数用在输出位置时,允许父类型替换子类型是不安全的,因为实现可能返回父类型的任何对象
interface AnimalClinic<T> {
fun getPatient(): T // 获取病患
}
val catClinic: AnimalClinic<Cat> = AnimalClinic()
// 如果允许 AnimalClinic<Animal> 当作 AnimalClinic<Cat> 使用
// 那么可能会返回一只狗给期望获得猫的代码,这显然是不对的
- 当类型参数只用在输入位置时,父类型替换子类型是安全的,因为能处理父类型就一定能处理子类型
interface AnimalTreatment<in T> {
fun treat(patient: T) // 只处理病患
}
val animalTreatment: AnimalTreatment<Animal> = AnimalTreatment()
// 允许 animalTreatment 当作 AnimalTreatment<Cat> 使用
// 因为能治疗所有动物的医生一定能治疗猫,这是安全的
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!