搞懂 kotlin 泛型 out 和 in 关键字

一、泛型的基本概念

泛型是一种编程机制,允许在定义类、接口或方法时使用类型参数。这样,在使用时可以指定具体的类型,从而实现代码的复用并提高类型安全性,避免了显式的类型转换和潜在的运行时错误。例如,List<String> 就明确了集合内只能存放字符串。

二、关键字out、in的使用

kotlin 复制代码
//out的使用
interface IContainer<out R>{
   fun get():R
}

//in的使用
interface IContainer<in V>{
   fun set(data:V)
}

三、泛型的进阶概念:型变 (Variance)、out (协变 - Covariant)、in (逆变 - Contravariant)

  • 不变 (Invariant) :泛型的默认行为,即使 AB 的子类,Container<A>Container<B> 之间也没有任何派生关系。
  • 型变 (## Variance) :是指泛型类型在基础类型发生变化时,其派生泛型类型如何跟随变化的性质。
  • 协变 (Covariant) :如果 AB 的子类,那么 Container<A> 被视为 Container<B> 的子类,则称之为协变。在 Kotlin 中使用 out 关键字,表示该容器只能作为"生产者"输出数据。
  • 逆变 (Contravariant) :如果 AB 的子类,那么 Container<B> 被视为 Container<A> 的子类,则称之为逆变。在 Kotlin 中使用 in 关键字,表示该容器只能作为"消费者"输入数据。

四、为什么需要out、in

举例说明:

kotlin 复制代码
interface Fruit // 定义"水果"类型
class Apple : Fruit // 定义"苹果"类型
class Banana : Fruit // 定义"香蕉"类型
class Container<T> // 定义容器

虽然 Apple 和 Banana 是Fruit 的子类,但是Container与Container、Container 之间没有任何关系,也不能相互赋值。

swift 复制代码
var fruitContainer: Container<Fruit> = Container<Fruit>()
var appleContainer: Container<Apple> = Container<Apple>()
val BananaContainer: Container<Banana> = Container<Banana>()

// 下面的写法都是错的,不能编译
fruitContainer = appleContainer
fruitContainer = BananaContainer
appleContainer = BananaContainer

当限制条件满足时,这种类型转换在逻辑上是符合直觉的,如下代码所示:

kotlin 复制代码
open class Fruit
class Apple : Fruit()
class Banana : Fruit()

abstract class Container<T> {
    abstract fun get(): T
}

var fruitContainer: Container<Fruit> = object : Container<Fruit>() {
    override fun get(): Fruit {
        return Fruit()
    }
}

var appleContainer: Container<Apple> = object : Container<Apple>() {
    override fun get(): Apple {
        return Apple()
    }
}

fruitContainer 预期的结果类型是 Fruit,而 appleContainer 只能提供 Apple,因为Apple 是 Fruit 的子类,从抽象逻辑上来看 fruitContainer = appleContainer 是可以的。但是编译器无法理解,因此需要显式地告诉编译器它们之间存在的关系。

使用 out 关键字定义协变关系(限定输出类型):

kotlin 复制代码
abstract class Container<out T> {
    abstract fun get(): T
}

var appleContainer: Container<Apple> = object : Container<Apple>() {
    override fun get(): Apple {
        return Apple()
    }
}
fruitContainer = appleContainer // 可以通过编译

使用 in 关键字定义逆变关系(限定输入的类型):

kotlin 复制代码
abstract class Container<in T> {
    abstract fun set(data: T)
}

var fruitContainer: Container<Fruit> = object : Container<Fruit>() {
    override fun set(data: Fruit) {
        TODO("Not yet implemented")
    }
}

// fruitContainer 是可以赋值给 appleContainer 的,可以通过编译
var appleContainer: Container<Apple> = fruitContainer
appleContainer.set(Apple())

五、核心意义

引入 outin 关键字是为了打破泛型默认的"不变性",在逻辑合理且类型安全的前提下,允许更灵活的类型转换与赋值。

相关推荐
恋猫de小郭1 小时前
Amper 正式转正 Kotlin Toolchain ,Gradle 未来何去何从
android·前端·flutter
plainGeekDev2 小时前
ButterKnife → ViewBinding
android·java·kotlin
成都大菠萝16 小时前
Android Car CarProperty 车辆信号链路
android
敲代码的鱼16 小时前
PDF 预览与签名批注写回 支持安卓 iOS 鸿蒙 UTS插件
android·前端·ios
时光足迹18 小时前
uni-app 视频通话实战:康复师与患者视频问诊的 6 个致命 Bug 与解决方案
android·ios·uni-app
Coffeeee1 天前
闲聊几句,Android老哥们,你们多久没做技改需求了
android·程序员·代码规范
萝卜er1 天前
Fragment 生命周期与状态恢复-《Android深水区(四)》
android
萝卜er1 天前
Intent 显式、隐式与 PendingIntent-《Android深水区(五)》
android
Kapaseker1 天前
一文吃透 Kotlin 集合操作符
android·kotlin
三少爷的鞋1 天前
Main-safe:现代Android 架构真正的分水岭
android