You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>,符合逆变规则。
  • 类的协变/逆变限制只作用于类内部定义的成员函数,因为内部函数属于类泛型契约的一部分;而扩展函数是外部逻辑,编译器会通过独立的泛型推导和子类型适配来处理,只要参数符合泛型子类型规则即可,不受类本身的协变/逆变声明约束。

内容的提问来源于stack exchange,提问作者lann stark

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 08:33:13