Kotlin 泛型与扩展:out/in 搞不懂?扩展函数到底扩展了啥?

Kotlin 泛型与扩展:out/in 搞不懂?扩展函数到底扩展了啥?

泛型和扩展函数是 Kotlin 进阶的必经之路。这两个特性看似复杂,但只要理解了核心思想,就能灵活运用。

1. 泛型基础:类型参数化

泛型类

kotlin 复制代码
// 定义:类型用 T 占位
class Box<T>(val value: T)

// 使用
val intBox = Box(123)      // 编译器推断为 Box<Int>
val stringBox = Box("abc") // 编译器推断为 Box<String>
val explicitBox = Box<String>("abc") // 显式指定

泛型函数

kotlin 复制代码
// 定义
fun <T> singleton(item: T): List<T> = listOf(item)

// 使用
val list = singleton(123)  // List<Int>

多类型参数

kotlin 复制代码
class PairBox<K, V>(val key: K, val value: V)

val pair = PairBox("age", 25)  // PairBox<String, Int>

2. 型变:泛型的继承关系

这是理解泛型的难点。先看痛点:

kotlin 复制代码
class Box<T>(val value: T)

// 虽然 String 是 Any 的子类
val strBox: Box<String> = Box("abc")
// 但 Box<String> 不是 Box<Any> 的子类!
// val anyBox: Box<Any> = strBox  ❌ 编译报错

协变:out 关键字(生产者)

out 标记,只能产出(返回),不能消费(传入):

kotlin 复制代码
// 协变类:T 只能用做返回值,不能用做参数
class FruitBox<out T>(val fruit: T) {
    fun getFruit(): T = fruit  // ✅ T 作为返回值
    // fun setFruit(t: T) {}   // ❌ T 不能作为参数
}

// 现在可以赋值了
val appleBox: FruitBox<String> = FruitBox("苹果")
val fruitBox: FruitBox<Any> = appleBox  // ✅

记忆口诀:生产者 out,产出用 out。

逆变:in 关键字(消费者)

in 标记,只能消费(传入),不能产出(返回):

kotlin 复制代码
// 逆变类:T 只能用做参数,不能用做返回值
class TrashBin<in T> {
    fun trash(item: T) = println("扔掉 $item")  // ✅ T 作为参数
    // fun getTrash(): T {}  // ❌ T 不能作为返回值
}

// 现在可以赋值了
val anyBin: TrashBin<Any> = TrashBin()
val strBin: TrashBin<String> = anyBin  // ✅

记忆口诀:消费者 in,消耗用 in。

PECS 原则

Producer-Extends, Consumer-Super

  • 需要产出 数据 → out(协变)
  • 需要消费 数据 → in(逆变)

3. 星号投射:未知类型的安全使用

当不知道具体类型时,用 *

kotlin 复制代码
// 协变类
class FruitBox<out T>(val fruit: T) {
    fun getFruit(): T = fruit
}

val box: FruitBox<*> = FruitBox("苹果")
val fruit = box.getFruit()  // fruit 是 Any?

规则

表格

原类型 Box<*> 等价于
Box<out T> Box<out Any?>
Box<in T> Box<in Nothing>
Box<T> Any? / 写 Nothing

4. 扩展函数:给现有类添方法

基本语法

kotlin 复制代码
// 给 String 添加一个方法
fun String.addExclamation() = this + "!"

// 使用
"Hello".addExclamation()  // "Hello!"

扩展函数原理

扩展函数不修改原类,只是让你能用「点」语法调用。编译器会把它编译成普通静态方法:

kotlin 复制代码
// Kotlin 写法
fun String.addExclamation() = this + "!"

// 编译后等价于
fun addExclamation(receiver: String) = receiver + "!"

扩展属性

kotlin 复制代码
// 扩展属性
val String.lastChar: Char
    get() = this[length - 1]

// 使用
"Hello".lastChar  // 'o'

5. 扩展函数实战

给 Context 扩展常用方法

kotlin 复制代码
// 扩展函数:toast
fun Context.toast(message: String, duration: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this, message, duration).show()
}

// 扩展函数:dp 转 px
fun Context.dp2px(dp: Float): Float {
    return dp * resources.displayMetrics.density
}

// 使用
toast("登录成功")
tvTitle.dp2px(16f)

给 View 扩展

kotlin 复制代码
// View 延迟操作
fun View.onClick(action: () -> Unit) {
    setOnClickListener { action() }
}

// 使用
button.onClick {
    toast("点击了")
}

// View 显示/隐藏
var View.visible: Boolean
    get() = visibility == View.VISIBLE
    set(value) {
        visibility = if (value) View.VISIBLE else View.GONE
    }

textView.visible = true

扩展集合操作

kotlin 复制代码
// 安全获取元素
fun <T> List<T>.safeGet(index: Int): T? {
    return if (index in indices) get(index) else null
}

// 使用
val list = listOf("A", "B", "C")
list.safeGet(5)  // null

6. 扩展的作用域

在其他类内部定义扩展(成员扩展)

kotlin 复制代码
class Person(val name: String) {
    // 在 Person 内部给 String 定义扩展
    fun String.sayHello() {
        println("$name 说:$this")
    }
    
    fun test() {
        "你好".sayHello()  // 只在 Person 内部可用
    }
}

// 在外部无法调用
// "你好".sayHello()  ❌

扩展接收者 vs 分发接收者

kotlin 复制代码
class Person {
    fun Person.extension() {
        // this = 扩展接收者(String)
        // this@Person = 分发接收者(Person)
    }
}

7. 泛型约束

上界约束

kotlin 复制代码
// T 必须继承 Comparable<T>
fun <T : Comparable<T>> maxOf(a: T, b: T): T {
    return if (a > b) a else b
}

多约束

kotlin 复制代码
// T 必须同时实现 Serializable 和 Comparable
fun <T> serialize(value: T) 
    where T : Serializable, T : Comparable<T> {
    // ...
}

常见问题

Q:扩展函数能被 override 吗?

A:不能。扩展函数不是类的真正成员,不能被重写。

Q:扩展函数和成员方法冲突怎么办?

A:成员方法优先。编译器会优先使用类本身定义的方法。

Q:什么时候用泛型约束 T : Bound

A:当泛型类型需要调用某些方法时,比如 T : Comparable<T> 让你能用 compareTo

总结

表格

概念 核心要点
泛型 类型参数化,编译时类型检查
协变 out 生产者,只能产出(返回),不能消费
逆变 in 消费者,只能消费(传入),不能产出
PECS Producer-Extends, Consumer-Super
星号投射 不知道具体类型时的安全通配符
扩展函数 给现有类添方法,不修改原类
相关推荐
plainGeekDev2 小时前
Kotlin 特殊类型篇:密封类比枚举好使在哪?Nothing 到底是个啥?
kotlin
沅霖4 小时前
Android Studio Java工程开发环境,怎么切换到Kotlin开发环境
android·kotlin·android studio
Kapaseker4 小时前
Kotlin SharedFlow 的三个参数到底有啥用
android·kotlin
阿巴斯甜5 小时前
by 和by lazy 懒加载
kotlin
三少爷的鞋7 小时前
Android 架构系列之MVVM 和 MVI 算架构吗?
android·kotlin
只可远观1 天前
Android 自动埋点(页面打开 / 关闭 + 点击事件)完整方案
android·kotlin
aqi001 天前
FFmpeg开发笔记(一百零二)国产的音视频移动开源工具FFmpegAndroid
android·ffmpeg·kotlin·音视频·直播·流媒体
阿巴斯甜1 天前
子协程的异常传播(CoroutineExceptionHandler ):
kotlin
alexhilton2 天前
Android上的ZeroMQ:用发布/订阅模式连接Linux服务
android·kotlin·android jetpack