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

Kotlin泛型函数中协变逆变的表现疑惑求解

Kotlin协变逆变的参数方差疑问

先看示例主函数:

fun main() {
    val sr = mutableListOf<Int>(1, 2, 3)
    val des = mutableListOf<Number>()
    copyData(sr, des)
    des.forEach { println(it) }
}

版本A的函数实现:

// A:
fun <T> copyData(source: MutableList<out T>,
                 destination: MutableList<T>) {
    for (item in source) {
        destination.add(item)
    }
}

我理解MutableList<out T>让MutableList在T上协变,这部分没问题。

但改成版本B后:

// B:
fun <T> copyData(source: MutableList<T>,
                 destination: MutableList<in T>) {
    for (item in source) {
        destination.add(item)
    }
}

我也明白destination: MutableList<in T>让MutableList在T上逆变,但搞不懂为什么此时source没标记out(生产型)却能从中取出item,代码还能正常运行。我有两个推测:

  • 仅在最后一个参数添加in修饰符时,第一个参数的T不受影响;
  • 在第一个参数添加方差修饰符时,后续所有参数的T都会受影响。

解答

首先得明确:函数的泛型参数T是编译器自动推导出来的,不是固定死的类型。

看你调用的场景:传入的sr是MutableList<Int>,des是MutableList<Number>。编译器会自动找最合适的T——这里T会被推导成Int,因为Int是两个类型里最具体的那个。

此时source的类型就是MutableList<Int>,从里面读取Int类型的元素本来就是完全合法的操作,根本不需要协变标记out。协变标记的作用不是“允许读取”,而是允许你传入T的子类型的列表作为source。比如在版本A里,T会被推导成Number,这时MutableList<Int>(Int是Number的子类型)能作为MutableList<out Number>传入,这才是out的作用。

而版本B里,destination: MutableList<in T>的作用是允许destination接受T的父类型的列表,比如这里T是Int,MutableList<Number>(Number是Int的父类型)符合MutableList<in Int>的约束,所以能正常添加Int元素。

再说说你的两个推测:

  • 第一个不对:每个参数的方差修饰符是独立的,只约束自身类型和T的关系,和参数顺序、其他参数没有关联。给destination加in,只是限制这个参数的类型范围,source的约束还是MutableList<T>,只要实际传入的类型符合推导后的T就行。
  • 第二个也不对:参数的方差修饰符只作用于自身,不会影响其他参数的T。比如版本A里source用out T,只是对source的类型做了协变约束,destination的MutableList<T>是独立的约束,两者互不影响。

核心逻辑是:从MutableList<T>读取元素本来就是该容器的基本操作,协变标记out的意义是扩展source的可接受类型范围,而非“开启读取权限”。比如你直接写val list = mutableListOf<Int>(); val num = list[0],不需要任何out就能读取,道理是一样的。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 15:15:10