Kotlin -> 普通Lambda vs 挂起Lambda

1. 普通Lambda vs 挂起Lambda的本质区别

1.1 普通Lambda(同步执行)

kotlin 复制代码
val lambda: (Int) -> String = { it.toString() }

// 编译器生成:
class Lambda$1 : Function1<Int, String> {
    override fun invoke(p1: Int): String {
        return p1.toString() // 直接返回结果
    }
}
  • 特点:
    • 同步执行:函数调用后立即返回结果
    • 不能挂起:执行过程中不会暂停
    • 简单映射:参数和返回类型直接对应
    • 单线程执行:在调用线程上同步完成

1.2 挂起Lambda(异步执行)

kotlin 复制代码
val suspendLambda: suspend (Int) -> String = { 
    delay(1000) // 可能挂起
    it.toString() 
}

// 编译器生成:
class SuspendLambda$1 : SuspendLambda, Function2<Int, Continuation<String>, Any?> {
    override fun invoke(p1: Int, continuation: Continuation<String>): Any? {
        // 可能返回实际结果或COROUTINE_SUSPENDED
    }
}
  • 特点:
    • 异步执行:可能不会立即返回结果
    • 可以挂起:遇到挂起点会暂停执行
    • CPS转换:需要额外的Continuation参数
    • 状态机:内部实现为状态机来处理挂起/恢复

2. CPS (Continuation Passing Style) 转换原理

2.1 什么是CPS

CPS是一种编程风格,函数不直接返回结果,而是将结果传递给一个continuation(继续执行的回调)

kotlin 复制代码
// 直接风格 (Direct Style)
fun add(a: Int, b: Int): Int = a + b

// CPS风格
fun addCPS(a: Int, b: Int, cont: (Int) -> Unit) {
    cont(a + b) // 将结果传递给continuation
}

2.2 Kotlin挂起函数的CPS转换

kotlin 复制代码
// 用户编写的挂起函数
suspend fun fetchUser(id: Int): User {
    val response = httpCall(id) // 可能挂起
    return parseUser(response)
}

// 编译器转换后的实际签名
fun fetchUser(id: Int, continuation: Continuation<User>): Any? {
    // 返回User或COROUTINE_SUSPENDED
}

3. 为什么需要额外的Continuation参数?

3.1 挂起和恢复机制

kotlin 复制代码
suspend fun example(): String {
    println("Before delay")
    delay(1000) // 挂起点
    println("After delay") 
    return "Done"
}

// 编译后的状态机逻辑(简化)
fun example(continuation: Continuation<String>): Any? {
    val sm = continuation as? ExampleStateMachine ?: ExampleStateMachine(continuation)
    
    when (sm.label) {
        0 -> {
            println("Before delay")
            sm.label = 1
            val result = delay(1000, sm) // 传递状态机作为continuation
            if (result == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
            // 如果没挂起,继续执行
        }
        1 -> {
            println("After delay")
            return "Done"
        }
    }
}

3.2 Continuation的作用

kotlin 复制代码
interface Continuation<in T> {
    val context: CoroutineContext
    fun resumeWith(result: Result<T>) // 恢复执行的回调
}
  • Continuation负责:
    • 保存执行状态:当前执行到哪个步骤
    • 恢复执行:异步操作完成后继续执行
    • 异常处理:传递异步操作中的异常
    • 上下文管理:携带协程上下文信息

4. 详细的转换规则对比

4.1 普通Lambda转换规则

kotlin 复制代码
// 原始类型:(P1, P2, ..., Pn) -> R
// 转换为:Function(n)<P1, P2, ..., Pn, R>

val lambda1: () -> Int = { 42 }
// 转换为:Function0<Int>

val lambda2: (String) -> Int = { it.length }
// 转换为:Function1<String, Int>

val lambda3: (Int, String) -> Boolean = { i, s -> i > 0 && s.isNotEmpty() }
// 转换为:Function2<Int, String, Boolean>

4.2 挂起Lambda转换规则

kotlin 复制代码
// 原始类型:suspend (P1, P2, ..., Pn) -> R
// 转换为:Function(n+1)<P1, P2, ..., Pn, Continuation<R>, Any?>

val suspendLambda1: suspend () -> Int = { 
    delay(1000)
    42 
}
// 转换为:Function1<Continuation<Int>, Any?>

val suspendLambda2: suspend (String) -> Int = { 
    delay(1000)
    it.length 
}
// 转换为:Function2<String, Continuation<Int>, Any?>

val suspendLambda3: suspend (Int, String) -> Boolean = { i, s ->
    delay(1000)
    i > 0 && s.isNotEmpty()
}
// 转换为:Function3<Int, String, Continuation<Boolean>, Any?>

4.3 带接收者的Lambda转换

kotlin 复制代码
// 普通带接收者Lambda
val receiverLambda: String.() -> Int = { this.length }
// 转换为:Function1<String, Int>

// 挂起带接收者Lambda
val suspendReceiverLambda: suspend String.() -> Int = { 
    delay(1000)
    this.length 
}
// 转换为:Function2<String, Continuation<Int>, Any?>

5. 为什么返回类型是Any?而不是具体类型?

5.1 挂起函数的两种返回情况

kotlin 复制代码
suspend fun maybeSupsend(): String {
    return if (someCondition) {
        "immediate result" // 立即返回
    } else {
        delay(1000) // 挂起,稍后恢复
        "delayed result"
    }
}

// 编译后
fun maybeSupsend(cont: Continuation<String>): Any? {
    return if (someCondition) {
        "immediate result" // 返回String
    } else {
        // 启动异步操作,返回挂起标记
        return COROUTINE_SUSPENDED // 返回特殊标记
    }
}

5.2 COROUTINE_SUSPENDED标记

kotlin 复制代码
// kotlin.coroutines.intrinsics
public val COROUTINE_SUSPENDED: Any = CoroutineSingletons.COROUTINE_SUSPENDED

// 使用示例
fun someFunction(): Any? {
    return when {
        canReturnImmediately -> "actual result"
        needsToSuspend -> COROUTINE_SUSPENDED
        else -> null
    }
}

6. 完整的对比示例

6.1 普通Lambda的完整流程

kotlin 复制代码
val lambda: (Int) -> String = { it.toString() }

// 编译生成
class Lambda$1 : Function1<Int, String> {
    override fun invoke(p1: Int): String {
        return p1.toString()
    }
}

// 调用流程
val result = lambda(42) // 直接调用invoke,立即得到结果 "42"

6.2 挂起Lambda的完整流程

kotlin 复制代码
val suspendLambda: suspend (Int) -> String = { 
    delay(1000)
    it.toString() 
}

// 编译生成
class SuspendLambda$1 : SuspendLambda, Function2<Int, Continuation<String>, Any?> {
    var label = 0
    var p$0: Int = 0 // 保存参数
    
    override fun invoke(p1: Int, continuation: Continuation<String>): Any? {
        val sm = create(p1, continuation) as SuspendLambda$1
        return sm.invokeSuspend(Unit)
    }
    
    override fun invokeSuspend(result: Result<Any?>): Any? {
        when (label) {
            0 -> {
                label = 1
                val delayResult = delay(1000, this)
                if (delayResult == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
            }
            1 -> {
                return p$0.toString()
            }
        }
    }
}

// 调用流程
// 1. 调用invoke(42, continuation)
// 2. 可能返回COROUTINE_SUSPENDED(挂起)
// 3. 异步操作完成后,通过continuation.resumeWith恢复
// 4. 最终得到结果 "42"

7. 总结:根本区别

特性 普通Lambda 挂起Lambda
执行模式 同步,立即返回 异步,可能挂起
参数转换 直接映射 额外添加Continuation
返回类型 保持原类型 改为Any?(支持挂起标记)
内部实现 简单函数调用 状态机 + CPS
继承关系 Function接口 SuspendLambda + Function接口
调用约定 直接调用 CPS调用约定
相关推荐
张拭心11 分钟前
春节后,有些公司明确要求 AI 经验了
android·前端·人工智能
张拭心21 分钟前
Android 17 来了!新特性介绍与适配建议
android·前端
Kapaseker3 小时前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
黄林晴3 小时前
Android17 为什么重写 MessageQueue
android
阿巴斯甜1 天前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker1 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab2 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android