Kotlin声明处协变/逆变的函数参数报错及扩展函数行为疑问
Kotlin泛型协变/逆变的两个疑问解答
问题1:逆变类Y的函数参数报错原因
先看代码示例:
class X<out T> { // 编译错误:Type parameter T is declared as 'out' but occurs in 'in' position in type X<T> fun doSomethingX(x: X<T>) { } } class Y<in T> { // 编译错误:Type parameter T is declared as 'in' but occurs in 'out' position in type Y<T> fun doSomethingY(y: Y<T>) { } }
协变类X<out T>的报错逻辑容易理解:X<T>作为函数参数,T处于in位置(参数是输入,会消费T类型相关的值),违反了协变泛型只能用于输出位置的规则。但逆变类Y<in T>中doSomethingY(y: Y<T>)的报错原因需要拆解:
原因解析
要明确泛型类自身的协变/逆变性质,对其作为参数时的类型位置影响:
- 类
Y声明为in T(逆变),意味着Y<父类型>是Y<子类型>的子类型(逆变是反向兼容)。 - 当
Y<T>作为函数参数时,本质是把整个Y<T>当作输入值传递,此时T的位置逻辑会反转:
假设T是Number,逆变规则允许Y<Number>接收Y<Int>实例,但如果允许doSomethingY(y: Y<T>)存在,就意味着可以把Y<Int>传入Y<Number>的该函数,这会导致T的范围被非法扩大,违反了逆变类只能消费T、不能输出T相关类型的核心契约。
直白来说:逆变类的泛型参数T只能直接出现在函数的输入参数类型中,不能出现在「以T为泛型参数的自身类型」作为输入的场景——因为此时T的位置等价于out位置,和逆变的规则冲突。
问题2:扩展函数不受类协变/逆变限制的原因
看代码示例:
fun main() { X<Number>(1).doSomething(X<Int>(2)) X<Int>(1).doSomething(X<Number>(2)) } class X<out|in T>(private val t: T) fun <R> X<R>.doSomething(x: X<R>) { println(this) println(x) }
无论X声明为out还是in,main中的调用都不会编译报错,核心原因如下:
原因解析
扩展函数是在类外部定义的独立工具函数,它的泛型参数R和类X自身的泛型参数是相互独立的:
- 调用时编译器会自动推导
R的类型,再通过泛型的子类型兼容规则适配参数:- 如果X是
out T(协变):X<Int>是X<Number>的子类型,调用X<Number>.doSomething(X<Int>)时,R被推导为Number,X<Int>可以安全向上转型为X<Number>,符合协变规则。 - 如果X是
in T(逆变):X<Number>是X<Int>的子类型,调用X<Int>.doSomething(X<Number>)时,R被推导为Int,X<Number>可以安全向下转型为X<Int>,符合逆变规则。
- 如果X是
- 类的协变/逆变限制只作用于类内部定义的成员函数,因为内部函数属于类泛型契约的一部分;而扩展函数是外部逻辑,编译器会通过独立的泛型推导和子类型适配来处理,只要参数符合泛型子类型规则即可,不受类本身的协变/逆变声明约束。
内容的提问来源于stack exchange,提问作者lann stark
相关产品推荐
相关产品推荐

