在 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不能作为返回值)。
⚖️ 三、核心区别与设计思想
|
特性 |
|
|
|---|---|---|
|
关键字 |
|
|
|
型变方向 |
子类关系与 |
子类关系与 |
|
数据流 |
输出(生产 |
输入(消费 |
|
读写限制 |
可读不可写 |
可写不可读 |
|
常见应用 |
|
|
🔧 设计本质:
-
协变:确保取出对象时,子类对象可安全视为父类(如
Cat可当Animal使用)。 -
逆变:确保写入对象时,父类容器可安全处理子类(如
Animal容器能处理Cat)。
🛠️ 四、实际应用场景
-
集合操作:安全复制不同类型集合
// 从只读源(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>。
-
-
回调处理:逆变实现灵活回调
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:标记生产者,放宽输出限制(子类泛型可赋给父类泛型)。 -
in:标记消费者,放宽输入限制(父类泛型可赋给子类泛型)。 -
核心安全机制:通过限制类型参数的输入/输出位置,编译器在泛型赋值时确保类型安全,避免运行时崩溃。
简单记忆:OUT = 输出(只取不存),IN = 输入(只存不取)
3089

被折叠的 条评论
为什么被折叠?



