Android 之 Kotlin 泛型中的out和 in

在 Kotlin 泛型中,out和 in是用于控制类型参数​​型变(Variance)​​ 的关键字,分别对应​​协变(Covariance)​​ 和​​逆变(Contravariance)​​。它们通过限制类型参数的输入/输出位置,在保证类型安全的前提下实现更灵活的泛型赋值。以下是核心解析:


🔄 一、out(协变):生产者角色,只读不写,只能获取,不能修改

  • ​含义​​:若 A是 B的子类型,则 Producer<A>是 Producer<B>的子类型。

  • ​位置限制​​:out T只能出现在​​输出位置​​(如函数返回值),禁止作为输入参数类型。

  • ​安全原理​​:因无法写入数据,避免了“将父类对象插入子类容器”的类型污染问题。

  • ​典型场景​​:数据生产者(如集合读取、工厂类)。

✅ 示例:协变接口
interface Producer<out T> {
    fun produce(): T  // T 仅作为返回值
}

val stringProducer: Producer<String> = object : Producer<String> {
    override fun produce() = "Hello"
}
val anyProducer: Producer<Any> = stringProducer  // 协变允许赋值
val value: Any = anyProducer.produce()  // 安全取出 String(也是 Any)

  • ​禁止操作​​:若在接口中定义 fun consume(item: T),编译器会报错(T不能作为参数)。


🔄 二、in(逆变):消费者角色,只写不读,只能修改,不能获取

  • ​含义​​:若 A是 B的子类型,则 Consumer<B>是 Consumer<A>的子类型(​​方向相反​​)。

  • ​位置限制​​:in T只能出现在​​输入位置​​(如函数参数),禁止作为返回值类型(除 Any?外)。

  • ​安全原理​​:因无法按泛型类型读取数据,避免了“期望子类却读到父类”的类型错误。

  • ​典型场景​​:数据消费者(如回调接口、集合写入)。

✅ 示例:逆变接口
interface Consumer<in T> {
    fun consume(item: T)  // T 仅作为参数
}

val anyConsumer: Consumer<Any> = object : Consumer<Any> {
    override fun consume(item: Any) = println(item)
}
val stringConsumer: Consumer<String> = anyConsumer  // 逆变允许赋值
stringConsumer.consume("Text")  // 安全写入 String(Any 可处理)

  • ​禁止操作​​:若定义 fun get(): T,编译器会报错(T不能作为返回值)。


⚖️ 三、核心区别与设计思想

​特性​

out(协变)

in(逆变)

​关键字​

out

in

​型变方向​

子类关系与 T一致(Producer<A> <: Producer<B>

子类关系与 T相反(Consumer<B> <: Consumer<A>

​数据流​

输出(生产 T

输入(消费 T

​读写限制​

可读不可写

可写不可读

​常见应用​

List<out T>Source<out T>

Comparable<in T>MutableList<in T>

🔧 设计本质:
  • ​协变​​:确保取出对象时,子类对象可安全视为父类(如 Cat可当 Animal使用)。

  • ​逆变​​:确保写入对象时,父类容器可安全处理子类(如 Animal容器能处理 Cat)。


🛠️ 四、实际应用场景

  1. ​集合操作​​:安全复制不同类型集合

    // 从只读源(out)复制到可写目标(in)
    fun copy(src: List<out String>, dest: MutableList<in String>) {
        for (item in src) dest.add(item)  // src 只读,dest 只写
    }

    • src用 out允许传递 List<SubString>

    • dest用 in允许传递 MutableList<Any>

  2. ​回调处理​​:逆变实现灵活回调

    nterface Handler<in T> { fun handle(event: T) }
    val logHandler: Handler<Any> = { event -> println(event) }
    val stringHandler: Handler<String> = logHandler  // 安全赋值
    stringHandler.handle("Error")  // 实际调用 logHandler


⚠️ 五、常见错误与规避

​错误场景​

​编译器提示​

​解决方法​

在 out类型中写入数据

Type mismatch

确保 out T不作为参数类型。

在 in类型中按 T读取

Cannot access 'XXX'

避免读取,或使用通配符(如 Any?)。

混用型变修饰符

Conflicting variance

统一设计为生产者或消费者角色


💎 六、总结

  • out​:标记生产者,​​放宽输出限制​​(子类泛型可赋给父类泛型)。

  • in​:标记消费者,​​放宽输入限制​​(父类泛型可赋给子类泛型)。

  • ​核心安全机制​​:通过限制类型参数的输入/输出位置,编译器在泛型赋值时确保类型安全,避免运行时崩溃。

简单记忆:​​OUT = 输出(只取不存),IN = 输入(只存不取)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值