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

Kotlin泛型方差(Generic variance)原理及代码场景困惑咨询

Kotlin泛型方差核心解析

你的困惑点

我还没完全搞懂Kotlin泛型方差的工作原理。代码里的类层级是 Any -> Mammal -> Cat,Any 是根超类型,copy 函数有个参数叫 from。

我对 out 和 in 关键字的理解是:out 允许引用任意子类型,只能产出不能消费;in 允许引用任意超类型,只能消费不能产出。但在 copytest 函数里调用 copy 时,我把 catList1 传给了 from 参数——这个参数带 out 关键字,按我之前的理解是不是只能传 catList2 的子类型?更让我懵的是,我见过矛盾的说法:Kotlin里给泛型加 out 意味着这个引用可以赋值给任意超类型。现在彻底搞混了,能不能从头给我讲明白?谢谢!

对应代码

class List2<ITEM> {
    val data = mutableListOf<ITEM>()

    fun get(n: Int): ITEM = data[n]

    fun add(item: ITEM) { data.add(item) }
}

fun <T> copy(from: List2<out T>, to: List2<T>) {

}

fun copytest() {
    val catList1 = List2<Cat>()
    val catList2 = List2<Cat>()
    val mammalList = List2<Mammal>()

    copy(catList1, mammalList)
}

核心概念拆解

1. 先搞懂:泛型的“不变性”是基础

默认情况下,泛型类型是不变的:哪怕 Cat 是 Mammal 的子类,List2<Cat> 和 List2<Mammal> 之间没有任何父子关系——你不能把 List2<Cat> 赋值给 List2<Mammal> 变量,反之也不行。out 和 in 就是用来打破这种限制,让泛型类型之间建立合理的父子关系。

2. 协变(out):生产者,只能读不能写

给泛型加 out,表示这个泛型类型是协变的:

  • 父子关系:List2<Cat> 是 List2<out Mammal> 的子类型(和普通类的父子关系一致,子类泛型可以向上转型为超类泛型)
  • 规则限制:只能从这个对象里取出数据(产出),不能存入数据(消费)——因为如果允许存入,会破坏类型安全(比如把 Dog 塞进原本存 Cat 的列表)

回到你的代码:
当你调用 copy(catList1, mammalList) 时,编译器自动推断出 T = Mammal,此时 from 的类型是 List2<out Mammal>。而 catList1 是 List2<Cat>,属于 List2<out Mammal> 的子类型,所以完全可以传入。这符合协变的逻辑:from 作为生产者,只会被用来读取数据(比如调用 from.get() 拿到 Cat,可以安全存入 to: List2<Mammal>,因为 Cat 是 Mammal 的子类)。

你之前的误解是搞反了方向:协变是允许把泛型子类型的对象传给需要泛型超类型的参数,而不是只能传子类型给它。比如 List2<out Mammal> 可以接受 List2<Cat>、List2<Mammal> 甚至 List2<Any>(如果层级允许)——这些都是它的子类型。

3. 逆变(in):消费者,只能写不能读

给泛型加 in,表示这个泛型类型是逆变的:

  • 父子关系:List2<Mammal> 是 List2<in Cat> 的子类型(和普通类的父子关系相反,超类泛型可以向下转型为子类泛型)
  • 规则限制:只能往这个对象里存入数据(消费),不能取出数据(产出)——因为取出的话类型不确定(比如从 List2<in Cat> 里取,可能拿到 Mammal 甚至 Any,没法安全赋值给 Cat)

4. 关于“加out可以赋值给任意超类型”的正确理解

这句话的意思是:如果有一个协变类型 List2<out Cat>,你可以把它赋值给 List2<out Mammal>、List2<out Any> 这类超类型的变量。比如:

val catList: List2<out Cat> = List2<Cat>()
val mammalList: List2<out Mammal> = catList // 合法,协变允许向上转型

这和普通类的向上转型逻辑完全一致:Cat 对象可以赋值给 Mammal 变量,协变让 List2<Cat> 对象可以赋值给 List2<out Mammal> 变量。

你的代码逻辑验证

你的 copy 函数中,from 用 out T 是合理的:它只需要作为生产者提供元素,不需要接受元素。而 to 用 List2<T>(不变),因为它需要接受元素(消费),如果需要读取也没问题。

如果尝试把 mammalList 传给 from 然后复制到 catList2,编译器会报错——因为 List2<Mammal> 不是 List2<out Cat> 的子类型,而且逻辑上也不安全:mammalList 里可能有 Dog,不能塞进 Cat 的列表。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 18:28:01