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

相关推荐
用户69371750013849 小时前
Android 开发,别只钻技术一亩三分地,也该学点“广度”了
android·前端·后端
唔669 小时前
原生 Android(Kotlin)仅串口「继承架构」完整案例二
android·开发语言·kotlin
错把套路当深情9 小时前
Kotlin 全方向开发技术栈指南
开发语言·kotlin
一直都在5729 小时前
MySQL索引优化
android·数据库·mysql
代码s贝多芬的音符11 小时前
android mlkit 实现仰卧起坐和俯卧撑识别
android
jwn99911 小时前
Laravel9.x核心特性全解析
android
今天又在写代码12 小时前
数据智能分析平台部署服务器
android·服务器·adb
梦里花开知多少13 小时前
深入谈谈Launcher的启动流程
android·架构
jwn99913 小时前
Laravel11.x新特性全解析
android·开发语言·php·laravel
我就是马云飞14 小时前
停更5年后,我为什么重新开始写技术内容了
android·前端·程序员