当前位置: 首页 > news >正文

Android学习总结之kotlin篇(二)

扩展函数转成字节码的原理(源码级别)

        Kotlin 扩展函数在编译时会被转换为静态方法,这一过程涉及到以下几个关键步骤:

        首先,Kotlin 编译器会为包含扩展函数的包生成一个特定的类。这个类的命名通常是基于包名和文件名的组合(如果未指定文件名,则遵循默认规则)。例如,对于我们之前的示例 package com.example.extensions,生成的类可能类似于 com/example/extensions/StringExtensionsKt

        然后,扩展函数会被转换为这个类中的静态方法。在这个静态方法中,会有一个额外的参数,这个参数代表扩展函数的接收者对象,在字节码中通常被命名为 $this。以 fun String.reverseAndAppend(): String 这个扩展函数为例,生成的静态方法可能如下:

package com.example.extensions;public class StringExtensionsKt {public static String reverseAndAppend(String $this) {return new StringBuilder($this).reverse().toString() + "!";}
}

        最后,当在 Kotlin 代码中调用扩展函数时,例如 val input = "hello"; val result = input.reverseAndAppend();,编译后的字节码会将这个调用转换为对上述生成的静态方法的调用,就像这样:

import com.example.extensions.StringExtensionsKt;public class Main {public static void main(String[] args) {String input = "hello";String result = StringExtensionsKt.reverseAndAppend(input);}
}

总结来说,Kotlin 扩展函数的本质是编译期的语法糖,它通过将扩展函数调用转换为静态方法调用,在不修改原类字节码的情况下,实现了对原类功能的扩展。

Kotlin 协程挂起和恢复的原理(源码级别)

挂起函数的编译

Kotlin 中的挂起函数在编译时会被转换为带有 Continuation 参数的普通函数。Continuation 是一个接口,定义了协程挂起后恢复执行的回调方法。

以下是一个简单的挂起函数示例:

suspend fun fetchData(): String {delay(1000)return "Data fetched"
}

编译后的代码大致如下(简化表示):

fun <T> fetchData(continuation: Continuation<String>): Any? {// 状态机相关逻辑when (continuation.label) {0 -> {continuation.label = 1return suspendCoroutineUninterceptedOrReturn<String> { cont ->// 执行 delay 操作delayInternal(1000L, cont)}}1 -> {// 处理延迟完成后的逻辑val result = continuation.result as Unitreturn "Data fetched"}else -> throw IllegalStateException("Unexpected label")}
}

这里使用了状态机模式,continuation.label 用于记录协程的执行状态。当协程执行到 delay 函数时,会挂起协程并将状态机的状态设置为 1,等待延迟完成。

协程的挂起和恢复
  • 挂起:当协程执行到挂起函数时,会调用 suspendCoroutineUninterceptedOrReturn 函数。这个函数会将当前的 Continuation 对象传递给挂起操作(如 delayInternal),并返回一个特殊值(通常是 COROUTINE_SUSPENDED)表示协程已经挂起。
suspend fun delay(timeMillis: Long) {return suspendCoroutineUninterceptedOrReturn { cont ->// 启动一个延迟任务Timer().schedule(timeMillis) {cont.resume(Unit)}COROUTINE_SUSPENDED}
}
  • 恢复

            当挂起操作完成后,会调用 Continuation 的 resume 或 resumeWithException 方法。在上面的 delay 示例中,当延迟时间到达时,会调用 cont.resume(Unit) 方法,将控制权交还给协程,协程会根据状态机的状态继续执行。

     

            例如,在 Android 中发起网络请求时,协程会将 “请求完成后需要执行的代码” 封装到 Continuation 的回调里,然后立即返回一个特殊值(COROUTINE_SUSPENDED),告诉协程框架 “我现在挂起了,后续逻辑等回调触发”。此时,承载协程的线程(如主线程)会被释放,去处理其他任务(如 UI 绘制),不会阻塞。当网络响应返回时,协程框架会调用 Continuation.resume() 方法,将结果传递回协程,协程根据 Continuation 中保存的状态,从挂起点继续执行后续代码(如解析数据、更新 UI)。整个过程通过状态机(编译生成的 when (label) 分支)管理不同挂起阶段的逻辑,避免回调嵌套(回调地狱)。

     

            在 ViewModel 或 Activity 中使用协程处理耗时操作时,挂起机制确保主线程不阻塞,避免 ANR。例如,withContext(Dispatchers.Main) 能安全切换回主线程更新 UI,其底层原理就是通过 Continuation 记录 “恢复时需要在主线程执行” 的状态,由协程调度器(如 HandlerDispatcher)实现线程切换。

面试扩展:

一、Kotlin 扩展函数转字节码的原理

Kotlin 扩展函数的本质是 编译期语法糖,其核心原理可概括为:将对扩展函数的调用转换为静态方法调用,不修改原类字节码,仅通过编译生成新的静态方法实现功能扩展。

  1. 语法糖的本质
    扩展函数看似是给原有类(如 Android 中的 ContextView)“新增成员函数”,但 Kotlin 不支持真正修改已编译的类(如 Java 类)。编译时,扩展函数会被编译成一个 独立的静态方法,该方法的第一个参数是扩展函数的 “接收者对象”(即被扩展的类实例,如 Context),相当于把 obj.extensionFunc() 转换为 ExtensionClass.extensionFunc(obj, 参数)

  2. 字节码层面的实现
    例如,给 TextView 写一个扩展函数 fun TextView.showMessage(msg: String),编译后会生成一个包含静态方法的类(如 TextViewExtKt.showMessage(TextView $this, String msg)),$this 代表调用扩展函数的对象本身。运行时,Kotlin 代码中的扩展函数调用会直接转为对这个静态方法的调用,与普通静态方法无异。

  3. 对 Android 开发的意义
    这种机制让开发者能在不修改 Android 框架类(如 ActivityFragment)的前提下,为其添加便捷方法(如链式调用设置控件属性),同时保持与 Java 代码的兼容性(Java 代码可直接调用生成的静态方法)。

扩展函数是否真正修改了原类?

        扩展函数并没有真正修改原类。它的本质是一个静态方法,原类的字节码并不会因为扩展函数的存在而发生改变。扩展函数通过第一个参数传入接收者对象,从而实现对原类功能的扩展。

扩展函数能否访问原类的私有成员?

        扩展函数不能访问原类的私有成员。它只能访问原类的公共成员,这与普通静态方法的权限是一致的。

二、Kotlin 协程挂起与恢复的原理

Kotlin 协程的挂起与恢复是实现 非阻塞异步编程 的核心,其原理可总结为:通过状态机和 Continuation 接口,在挂起点保存执行状态,恢复时按状态继续执行,全程不阻塞线程。

  1. 挂起函数的本质
    挂起函数(suspend fun)在编译时会被转换为一个接受 Continuation 参数的函数。Continuation 是一个接口,用于记录协程的 执行状态和恢复逻辑,包含一个 resume 方法,当协程恢复时调用。

  2. 挂起过程(以网络请求为例)

    • 当协程执行到挂起函数(如 withContext(Dispatchers.IO) 或 delay())时,会暂停当前执行,将当前的 局部变量、执行位置等状态 保存到 Continuation 中。
    • 例如,在 Android 中发起网络请求时,协程会将 “请求完成后需要执行的代码” 封装到 Continuation 的回调里,然后立即返回一个特殊值(COROUTINE_SUSPENDED),告诉协程框架 “我现在挂起了,后续逻辑等回调触发”。
    • 此时,承载协程的线程(如主线程)会被释放,去处理其他任务(如 UI 绘制),不会阻塞
  3. 恢复过程(以请求完成为例)

    • 当挂起操作完成(如网络响应返回、延迟时间到达),协程框架会调用 Continuation.resume() 方法,将结果传递回协程。
    • 协程根据 Continuation 中保存的状态,从挂起点继续执行后续代码(如解析数据、更新 UI)。整个过程通过 状态机(编译生成的 when (label) 分支) 管理不同挂起阶段的逻辑,避免回调嵌套(回调地狱)。
  4. Android 中的关键应用

    • 在 ViewModel 或 Activity 中使用协程处理耗时操作时,挂起机制确保主线程不阻塞,避免 ANR。
    • withContext(Dispatchers.Main) 能安全切换回主线程更新 UI,其底层原理就是通过 Continuation 记录 “恢复时需要在主线程执行” 的状态,由协程调度器(如 HandlerDispatcher)实现线程切换。

三、面试高频考点总结

  1. 扩展函数的核心考点

    • 问:“扩展函数是否真正修改了原类?”
      答:否,本质是静态方法,原类字节码不变,通过第一个参数传入接收者对象。
    • 问:“扩展函数能否访问原类的私有成员?”
      答:不能,仅能访问原类的公共成员(与普通静态方法权限一致)。
  2. 协程挂起的核心考点

    • 问:“协程挂起为什么不阻塞线程?”
      答:挂起时通过 Continuation 保存状态并释放线程,恢复时由协程框架调度继续执行,线程可复用。
    • 问:“suspend 关键字的作用是什么?”
      答:标记函数为挂起函数,允许在其中使用挂起操作(如 delaywithContext),编译时生成带 Continuation 参数的状态机代码。

扩展追问:

当在 Java 代码中调用 Kotlin 可空参数函数并传入 null 时

1. Kotlin 函数参数为可空类型

若 Kotlin 函数的参数被定义成可空类型(类型后面带 ?),Java 代码传入 null 是允许的,并且不会引发异常。在 Kotlin 函数里,需要对传入的可空参数进行空检查。

以下是示例代码:

Kotlin 代码
// 定义一个参数为可空类型的函数
fun processNullableParam(name: String?) {if (name != null) {println("Name is: $name")} else {println("Name is null")}
}
Java 代码
public class JavaCallKotlin {public static void main(String[] args) {// 调用 Kotlin 可空参数函数并传入 nullMainKt.processNullableParam(null); }
}

在上述代码中,Kotlin 函数 processNullableParam 的参数 name 是可空类型 String?,Java 代码传入 null 时,Kotlin 函数会对 name 进行空检查,然后根据情况输出相应信息。

2. Kotlin 函数参数为非可空类型

如果 Kotlin 函数的参数定义为非可空类型(类型后面不带 ?),Java 代码传入 null 会在运行时抛出 NullPointerException。这是因为 Kotlin 的非可空类型在编译时会保证其不为 null,但 Java 没有这种严格的类型检查。

以下是示例代码:

Kotlin 代码
// 定义一个参数为非可空类型的函数
fun processNonNullParam(name: String) {println("Name is: $name")
}
Java 代码
public class JavaCallKotlin {public static void main(String[] args) {// 调用 Kotlin 非可空参数函数并传入 nullMainKt.processNonNullParam(null); }
}

在上述代码中,Kotlin 函数 processNonNullParam 的参数 name 是非可空类型 String,当 Java 代码传入 null 时,运行时会抛出 NullPointerException

总结

在 Java 调用 Kotlin 函数时,需要留意 Kotlin 函数参数的可空性。若参数为可空类型,传入 null 是安全的;若参数为非可空类型,传入 null 会在运行时抛出异常。

http://www.dtcms.com/a/191339.html

相关文章:

  • 前端3D动画库
  • [Java实战]Spring Boot 3整合JWT实现无状态身份认证(二十四)
  • 18前端项目----Vue项目收尾优化|重要知识
  • ubuntu studio 系统详解
  • Spring Boot拦截器详解:原理、实现与应用场景
  • 计算机过程控制干燥操作实训装置JG-SX210化工单元操作实训装置
  • JavaScript 中级进阶技巧之map函数
  • 【嵌入式笔记】Modbus TCP
  • git仓库初始化
  • zabbix7.2最新版本 nginx自定义监控(三) 设置触发器
  • Anki 学习法
  • 深入浅出 IPFS 在 DApps 和 NFT 中的应用:以 Pinata 实战为例
  • 印度尼西亚数据源对接技术指南
  • vue3基础学习(上) [简单标签] (vscode)
  • 基于单片机的车灯智能控制系统设计与实现
  • 嵌入式中深入理解C语言中的指针:类型、区别及应用
  • rag文本切块
  • 算法备案如何判断自己的产品是否具备舆论属性
  • 开源Heygem本地跑AI数字人视频教程
  • 彻底解决QT5 中文编译不过问题
  • 《Python星球日记》 第70天:Seq2Seq 与Transformer Decoder
  • 为什么我不能获取到镜像,ImagePullBackoff
  • archliunx关闭自动休眠
  • 使用 Semantic Kernel 调用 Qwen-VL 多模态模型
  • Spring Boot 自动装配技术方案书
  • 什么是alpaca 或 sharegpt 格式的数据集?
  • QT之QComboBox组件
  • AbMole解读:脂质体的关键组分和主要合成方法
  • 致远OA项目管理应用包简介【附百度网盘链接】
  • C++ 并发编程(1)再学习,为什么子线程不调用join方法或者detach方法,程序会崩溃? 仿函数的线程启动问题?为什么线程参数默认传参方式是值拷贝?