Android随笔-kotlin 高阶函数

Kotlin 的高阶函数(Higher-Order Function)是指以函数作为参数,或者返回值为函数的函数。

一、两种基本形式

1. 函数作为参数

kotlin 复制代码
// 定义:接收一个函数参数
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
}

// 使用:传入 Lambda
val sum = calculate(3, 4) { x, y -> x + y }      // 7
val product = calculate(3, 4) { x, y -> x * y }  // 12

2. 函数作为返回值

kotlin 复制代码
// 定义:返回一个函数
fun multiplier(factor: Int): (Int) -> Int {
    return { number -> number * factor }
}

// 使用
val triple = multiplier(3)
println(triple(5))  // 15

二、为什么需要高阶函数?

代码复用与抽象:把变化的行为抽出来,骨架保持不变。

kotlin 复制代码
// 通用模板:只关心"执行前后"的逻辑
fun measureTime(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    val end = System.currentTimeMillis()
    println("耗时:${end - start} ms")
}

// 使用:传入任意逻辑
measureTime {
    // 这里放你想测量的代码
    Thread.sleep(1000)
}

Kotlin 标准库中的经典高阶函数

函数 作用 示例
let 非空执行 + 转换 str?.let { it.length }
run 执行代码块并返回结果 obj.run { name.length }
apply 配置对象并返回自身 TextView().apply { text = "Hi" }
also 副作用操作 list.also { println(it) }
with 对对象批量操作 with(buffer) { append("a"); append("b") }
filter / map 集合操作 list.filter { it > 0 }

这些本质上都是内联高阶函数(inline),编译时会展开,没有额外函数调用开销。

三、内联优化:inline 关键字

inline 的本质是编译期代码替换(Code Inlining )。它不是运行时的优化,而是编译器直接把函数体和 Lambda 代码"复制粘贴"到调用处。

当高阶函数配合 Lambda 使用时,每次调用都会产生一个匿名类的对象(带来性能开销)。用 inline 可以消除这个开销:

kotlin 复制代码
inline fun repeatAction(times: Int, action: (Int) -> Unit) {
    for (i in 0 until times) {
        action(i)
    }
}

// 编译后,Lambda 代码直接嵌入调用处,不生成额外对象
repeatAction(3) { println("第 $it 次") }

注意:inline 适合短小、频繁调用的高阶函数;如果 Lambda 参数被存储(而非立即调用),则不能用 inline。

1. 非 inline 高阶函数的编译结果

kotlin 复制代码
fun measure(block: () -> Unit) {
    println("start")
    block()
    println("end")
}

fun main() {
    measure { println("hello") }
}

编译后(近似 Java 字节码):

java 复制代码
// 1. 编译器为 Lambda 生成匿名类
class Main$1 implements Function0<Unit> {
    public Unit invoke() {
        println("hello");
        return Unit.INSTANCE;
    }
}

// 2. 调用处:创建对象 + 方法调用
void main() {
    measure(new Main$1());  // ① 创建函数对象
}

// 3. 高阶函数本身:虚方法调用
void measure(Function0<Unit> block) {
    println("start");
    block.invoke();         // ② 虚方法调用(invoke)
    println("end");
}

开销来源

  1. 对象分配:new Main$1() --- Lambda 被编译成匿名类实例
  2. 方法调用:measure() 本身是一次函数调用
  3. 虚方法分派:block.invoke() 是接口虚调用,JVM 无法内联优化
  4. 装箱/捕获开销:如果 Lambda 引用了外部变量,这些变量要封装进匿名类的字段

2. 加了 inline 后的编译结果

kotlin 复制代码
inline fun measure(block: () -> Unit) {
    println("start")
    block()
    println("end")
}

fun main() {
    measure { println("hello") }
}

编译后,main 函数直接变成:

java 复制代码
void main() {
    println("start");
    println("hello");   // Lambda 代码直接嵌入,没有 invoke 调用
    println("end");
}

3. 消除了哪些具体开销?

开销类型 非 inline inline 说明
函数对象创建 new Function0() ❌ 无 不需要生成匿名类,不分配堆内存
方法调用 调用 measure() ❌ 无 函数体直接展开在调用处
Lambda 调用 block.invoke() ❌ 无 Lambda 代码直接嵌入,无虚方法分派
变量捕获装箱 封装到匿名类字段 ❌ 无 外部变量直接以内联方式使用
栈帧创建 ❌ 无 不进入新的函数栈帧

4. 直观对比

复制代码
非 inline 调用链:
main() → measure() → block.invoke() → println()
   ↑         ↑            ↑
  栈帧      栈帧         虚方法分派

inline 调用链:
main() → println("start") → println("hello") → println("end")
   ↑
  只有一个栈帧,全部顺序执行

5. 代价(Trade-off)

inline 不是免费的,它用代码体积换执行效率

  • 每处调用都会复制一份函数体,导致生成的字节码变大(Code Bloat)
  • 如果函数体很大或被大量调用,会显著增加包体积
  • 因此 Kotlin 官方建议:只给短小、高频的高阶函数加 inline

四、优点

优点 说明
代码复用 把变化的行为抽成参数,骨架代码复用(如 measureTimerepeatAction
声明式编程 描述"做什么"而非"怎么做",代码更简洁(如 list.filter { it.age > 18 }
语法简洁 配合 Lambda,避免大量接口+实现类的样板代码
标准库丰富 let/run/apply/also/withfilter/map/reduce 等全是高阶函数
内联优化 inline 关键字可消除 Lambda 对象创建的开销,兼顾简洁与性能

五、缺点 & 坑点

缺点 说明
性能开销 inline 的高阶函数,每次调用会创建匿名类对象(Kotlin 1.5+ 对非捕获 Lambda 有优化,但捕获变量时仍有开销)
可读性下降 Lambda 嵌套过深(如回调地狱)会让代码难读
调试困难 堆栈信息里全是 Lambda$1$inline$ 之类的符号,断点定位麻烦
非局部返回陷阱 Lambda 里直接写 return 会返回到外层函数,容易误用
类型推断局限 复杂泛型 + 高阶函数组合时,编译器推断失败,需要显式声明类型
与 Java 互操作 Java 调用 Kotlin 高阶函数时语法不友好(需传入 FunctionN 实例)

六、常见问题

Q1:什么是高阶函数?和普通函数的区别?

接收函数作为参数,或返回值为函数的函数。普通函数操作的是数据,高阶函数操作的是行为。

Q2:inline 的作用是什么?为什么要内联?

inline 在编译期将函数体和 Lambda 代码直接插入调用处,消除函数调用和匿名对象创建的开销。适合短小、频繁调用的高阶函数。

Q3:inline、noinline、crossinline 的区别?

关键字 作用
inline 函数内联展开,Lambda 参数默认也内联
noinline 标记某个 Lambda 参数不内联(需要作为对象存储或传递时使用)
crossinline Lambda 禁止非局部返回(用于内联函数中,Lambda 在另一个上下文执行的情况,如线程切换)

Q4:什么是非局部返回(Non-local return)?

内联函数 的 Lambda 中直接使用 return,会返回到调用该内联函数的外层函数,而非仅退出 Lambda。这是内联特性带来的特殊行为。

kotlin 复制代码
fun foo() {
    run {  // inline 函数
        return  // 直接返回到 foo(),不是只退出 run!
    }
    println("不会执行")
}

Q5:let、run、apply、also、with 的区别?

函数 接收者 参数 返回值 典型用途
let it T.() -> R Lambda 结果 非空判断 + 转换
run this T.() -> R Lambda 结果 对象计算并返回结果
apply this T.() -> Unit 对象自身 对象配置(Builder 模式)
also it T.() -> Unit 对象自身 副作用(打印日志)
with this T.() -> R Lambda 结果 对同一对象批量操作

带 a 的(apply、also)返回自身;带 t 的(let)用 it;带 this 的是扩展接收者。

Q6:高阶函数在编译后是什么样子的?

非内联的高阶函数编译后,Lambda 会被编译成匿名类(实现 FunctionN 接口),捕获的变量作为其成员字段。inline 函数则直接展开代码,不生成额外类。

Q7:Kotlin 的函数类型和 Java 的 SAM 接口有什么区别?

  • Kotlin 的函数类型(如 (Int) -> Int)是编译器层面的语法糖,底层是 Function0、Function1 等接口。
  • Java 的 SAM(Single Abstract Method)接口需要显式定义接口类型。
  • Kotlin 支持自动 SAM 转换(Java 接口只有一个抽象方法时,可用 Lambda 替代),但 Kotlin 接口不支持 SAM 转换(需用函数类型或显式实现)。

Q8: 实现一个类似 measureTime 的高阶函数,要求内联,并支持返回耗时和结果。

kotlin 复制代码
inline fun <T> measureTimeMillis(block: () -> T): Pair<T, Long> {
    val start = System.currentTimeMillis()
    val result = block()
    val cost = System.currentTimeMillis() - start
    return result to cost
}

// 使用
val (data, time) = measureTimeMillis {
    fetchDataFromNetwork()
}

七、总结

Kotlin 高阶函数是函数式编程的基石,通过将行为参数化 实现代码复用;配合 inline 消除性能开销,但需注意非局部返回和可读性问题。标准库中的 let/run/apply 等本质上都是内联高阶函数

相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
ai2work3 天前
ch23 综合复刻:从零做一个最小可用版本(capstone)
kotlin
其实防守也摸鱼3 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
ai2work3 天前
ch21 签名、校验与发版
kotlin
AFinalStone3 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui