在 Kotlin 中,out 和 in 是用于控制泛型变型(variance)的关键字,它们决定了类型参数如何在子类型关系中传递。
1. out 关键字(协变)
基本概念
- 表示只读/生产操作
- 类型参数只能出现在输出位置(返回值)
Producer<out T>意味着如果A是B的子类型,那么Producer<A>是Producer<B>的子类型
示例
// 定义协变接口
interface Producer<out T> {
fun produce(): T // T 只能作为返回类型
// 错误:不能将 T 作为参数类型
// fun consume(item: T)
}
class Animal
class Dog : Animal()
val animalProducer: Producer<Animal> = object : Producer<Dog> {
override fun produce(): Dog = Dog()
}
// 这是允许的,因为 Producer<Dog> 可以赋值给 Producer<Animal>
2. in 关键字(逆变)
基本概念
- 表示只写/消费操作
- 类型参数只能出现在输入位置(参数)
Consumer<in T>意味着如果A是B的子类型,那么Consumer<B>是Consumer<A>的子类型
示例
// 定义逆变接口
interface Consumer<in T> {
fun consume(item: T) // T 只能作为参数类型
// 错误:不能将 T 作为返回类型
// fun produce(): T
}
val dogConsumer: Consumer<Dog> = object : Consumer<Animal> {
override fun consume(item: Animal) {
println("消费动物")
}
}
// 这是允许的,因为 Consumer<Animal> 可以赋值给 Consumer<Dog>
3. 实际应用场景
协变示例 - Kotlin 集合
// List 在 Kotlin 中是协变的
val dogs: List<Dog> = listOf(Dog(), Dog())
val animals: List<Animal> = dogs // 允许,因为 List<out T>
// 但 MutableList 是不变的
val mutableDogs: MutableList<Dog> = mutableListOf()
// val mutableAnimals: MutableList<Animal> = mutableDogs // 错误!
逆变示例 - 比较器
interface Comparator<in T> {
fun compare(a: T, b: T): Int
}
val animalComparator: Comparator<Animal> = Comparator { a, b ->
// 比较逻辑
}
val dogComparator: Comparator<Dog> = animalComparator // 允许,Comparator<in T>
4. 使用位置变型(Use-site Variance)
除了在声明处使用 out/in,还可以在类型使用时指定:
// 函数参数中的使用位置变型
fun copy(from: Array<out Animal>, to: Array<Animal>) {
// from 只能读取,不能写入
val animal: Animal = from[0]
// from[0] = Dog() // 错误!
}
// 同时使用协变和逆变
fun <T> copyWithCondition(
source: Array<out T>,
destination: Array<in T>,
condition: (T) -> Boolean
) {
// source 只能读,destination 可以写
}
5. 对比总结
| 特性 | out (协变) | in (逆变) | 不变 |
|---|---|---|---|
| 方向 | 子 → 父 | 父 → 子 | 无关系 |
| 位置 | 输出位置 | 输入位置 | 任意位置 |
| 语义 | 生产者 | 消费者 | 生产者+消费者 |
| 示例 | List<out T> | Comparator<in T> | MutableList<T> |
6. 记忆技巧
-
PECS(Producer-Extends, Consumer-Super)原则:
- 如果你是生产者(读取数据),用
out - 如果你是消费者(写入数据),用
in
- 如果你是生产者(读取数据),用
-
简单记法:
out= 只能输出(return)in= 只能输入(parameter)
7. 注意事项
// 错误示例
class Container<out T> {
// var item: T // 错误!属性默认有 getter 和 setter
// 正确:使用 private set
var item: T? = null
private set
// 正确:val 属性
val value: T? get() = null
}
8. 实际项目中的应用
// 数据仓库接口示例
interface Repository<out T : Entity, in ID> {
fun findAll(): List<T> // T 作为输出
fun findById(id: ID): T? // ID 作为输入
fun save(entity: @UnsafeVariance T) // 使用注解绕过检查
}
// 事件处理系统
interface EventHandler<in E> {
fun handle(event: E)
}
理解 out 和 in 的关键在于:它们不是约束类型本身,而是约束类型参数的使用方式,确保类型安全的同时提供灵活性。