协变与逆变(out 和 in)
1. 泛型的不变性问题
在默认情况下,Kotlin 的泛型是不变的(invariant)。这意味着即使类型 B 是类型 A 的子类型,Generic<B> 也不是 Generic<A> 的子类型。例如:
class Box<T>(var item: T)
val stringBox: Box<String> = Box("Hello")
// 编译错误:类型不匹配
val anyBox: Box<Any> = stringBox
这种设计是为了保证类型安全,但限制了泛型的灵活性。
2. 协变(out)
协变(covariance)允许泛型类型参数在子类型关系中保持相同的方向。使用 out 关键字标记类型参数:
class Producer<out T>(private val item: T) {
fun produce(): T = item
}
val stringProducer: Producer<String> = Producer("Kotlin")
// 合法:Producer<String> 是 Producer<Any> 的子类型
val anyProducer: Producer<Any> = stringProducer
关键规则:
out表示类型参数只能作为输出(返回值)- 禁止作为函数参数(除了构造函数)
3. 逆变(in)
逆变(contravariance)允许泛型类型参数在子类型关系中反转方向。使用 in 关键字标记类型参数:
class Consumer<in T> {
fun consume(item: T) {
println("Consuming $item")
}
}
val anyConsumer: Consumer<Any> = Consumer()
// 合法:Consumer<Any> 是 Consumer<String> 的子类型
val stringConsumer: Consumer<String> = anyConsumer
关键规则:
in表示类型参数只能作为输入(函数参数)- 禁止作为返回值类型
4. 使用场景对比
| 特性 | 关键字 | 类型关系 | 典型应用场景 |
|---|---|---|---|
| 协变 | out | Producer<Cat> → Producer<Animal> | 生产者类(如集合读取) |
| 逆变 | in | Comparator<Animal> → Comparator<Cat> | 消费者类(如比较器) |
5. 类型投影(Use-site Variance)
当无法修改类声明时,可以在使用处指定型变:
fun copy(from: Array<out Any>, to: Array<Any>) { /*...*/ }
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
copy(ints, any) // 允许协变
6. 星投影(Star Projection)
当类型参数未知时使用 *:
fun printItems(list: List<*>) {
list.forEach { println(it) }
}
等价于 List<out Any?>,表示不确定具体类型但安全读取。
7. 实际应用示例
// 协变示例:Kotlin 的 List 接口
public interface List<out E> : Collection<E> {
// 只有返回 E 的方法
}
// 逆变示例:Comparable 接口
public interface Comparable<in T> {
operator fun compareTo(other: T): Int
}
最佳实践:优先考虑声明处型变(
class Box<out T>),只有在无法修改类声明时才使用使用处型变(Box<out T>)。
