搞懂 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 关键字是为了打破泛型默认的"不变性",在逻辑合理且类型安全的前提下,允许更灵活的类型转换与赋值。

相关推荐
故渊at8 小时前
第二板块:Android 四大组件标准化学理 | 第八篇:Service 后台执行实体与优先级
android·gitee·service·前台服务·后台服务
会Tk矩阵群控的小木8 小时前
安卓群控系统对于游戏工作室实战教程
android·运维·游戏·adb·开源软件·个人开发
qeen879 小时前
【C++】类与对象之类的默认成员函数(二)
android·c语言·开发语言·c++·笔记·学习
故渊at9 小时前
第二板块:Android 四大组件标准化学理 | 第九篇:BroadcastReceiver 事件分发与有序广播
android·gitee·broadcast·广播·动态注册·静态注册
JohnnyDeng949 小时前
【Android】Room 数据库高级用法与性能调优:从查询瓶颈到毫秒级响应
android·性能优化·kotlin·room
zeqinjie10 小时前
Flutter 折叠屏 iPad / 宽屏适配实践
android·前端·flutter
ab_dg_dp10 小时前
Android 17+ 提取 AIDL 生成 Java 文件的实用脚本
android·java·python
Arrom10 小时前
DLNA 渲染端排障实战:从 20s 卡顿到 stale subscriber 的两周追凶之旅
android·java
_李小白11 小时前
【android opencv学习笔记】Day 32:直线检测之霍夫变换
android·opencv·学习