Kotlin泛型方差(Generic variance)原理及代码场景困惑咨询
你的困惑点
我还没完全搞懂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

