2-2-12 快速掌握Kotlin-out与in的使用

39 阅读2分钟

在 Kotlin 中,outin 是用于控制泛型变型(variance)的关键字,它们决定了类型参数如何在子类型关系中传递。

1. out 关键字(协变)

基本概念

  • 表示只读/生产操作
  • 类型参数只能出现在输出位置(返回值)
  • Producer<out T> 意味着如果 AB 的子类型,那么 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> 意味着如果 AB 的子类型,那么 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)
}

理解 outin 的关键在于:它们不是约束类型本身,而是约束类型参数的使用方式,确保类型安全的同时提供灵活性。