求协变与逆变的隐喻解释及Kotlin函数场景原理剖析
用「快递站隐喻」搞懂Kotlin函数的协变、逆变与不变性
先上好记的隐喻
把类的继承和函数的类型替换,比作日常的快递站逻辑:
- 类的继承:把
Animal看成「大号包裹」,Dog是「小号包裹」(小号能塞进大号,反之不行);把Number看成「大号箱子」,Int是「小号箱子」(小号箱子属于大号箱子的范畴) - 函数参数:相当于快递站的「收件台」——如果收件台能收大号包裹,那肯定能收小号包裹(因为小号包裹的尺寸在大号的范围内)
- 函数返回值:相当于快递站的「寄件箱」——如果寄件箱能寄出小号箱子,那肯定能满足需要大号箱子的需求(因为小号箱子本身就是大号箱子的一种)
Kotlin函数的类型规则:Function<in T, out R>
Kotlin里的函数类型(T) -> R本质对应Function<in T, out R>接口,其中:
in T:参数类型T是逆变的。简单说:如果A是B的父类,那么Function<A, R>可以替代Function<B, R>使用(对应收件台逻辑:能收大号包裹的台,兼容小号包裹)out R:返回值类型R是协变的。简单说:如果C是D的子类,那么Function<T, C>可以替代Function<T, D>使用(对应寄件箱逻辑:能寄小号箱子的,兼容大号箱子的需求)
结合你的代码拆解
基础代码结构
open class Animal class Dog : Animal() fun doSomething(fn: (Dog) -> Number) {}
这里doSomething需要的函数是:能接收Dog(小号包裹),返回Number(大号箱子)的函数,对应Function<Dog, Number>。
为什么传入animalToIntMethod能运行?
看要传入的函数:
val animalToIntMethod: (Animal) -> Int = { TODO() } doSomething(animalToIntMethod)
我们分参数和返回值两部分验证规则:
- 参数部分(逆变验证):
animalToIntMethod的参数是Animal(大号包裹),而doSomething要求的参数是Dog(小号包裹)。根据逆变规则,能处理所有Animal的函数,必然能处理Dog(毕竟Dog是Animal的子类)——就像能收大号包裹的收件台,绝对能收下小号包裹,完全没问题。 - 返回值部分(协变验证):
animalToIntMethod返回Int(小号箱子),而doSomething要求返回Number(大号箱子)。根据协变规则,返回Int等价于返回了一个Number(因为Int继承自Number)——就像快递站寄了个小号箱子,完全满足对方要大号箱子的需求(小号属于大号的范畴)。
一句话总结
- 逆变(
in):参数用父类替代子类,函数能处理更大范围的输入,自然兼容小范围 - 协变(
out):返回值用子类替代父类,更具体的结果能满足更宽泛的需求 - 不变性:如果函数类型既没有
in也没有out,那必须严格匹配类型,不能替换
内容的提问来源于stack exchange,提问作者Nadar
相关产品推荐
相关产品推荐

