2、高阶函数:从定义到实战
各位同学,今天我们来聊聊高阶函数。说实话,这个概念刚接触时我也觉得有点绕,但用顺手了就会发现——它其实是 Kotlin 最性感的特性之一。
2.1 什么是高阶函数?
先给个官方定义:高阶函数就是接收函数作为参数,或者返回一个函数的函数。说白了,函数也能像变量一样传来传去。
我刚开始学的时候,脑子里总有个疑问:函数不就是一段代码吗?怎么还能当参数?后来在项目里写回调写吐了,才明白高阶函数有多香。
核心要点:在 Kotlin 中,函数是一等公民。你可以把它赋值给变量,塞进集合,甚至从另一个函数里蹦出来。
2.2 函数类型——给函数一个“身份证”
要让函数当参数,首先得有个类型来描述它。Kotlin 用 (参数类型) -> 返回类型 这种语法来表示函数类型。
// 一个接收 Int 返回 String 的函数类型
(Int) -> String
// 两个参数,返回 Boolean
(Int, String) -> Boolean
// 没有参数,返回 Unit(相当于 void)
() -> Unit
我在项目中见过有人把函数类型写得很长,比如 (Context, String, Int) -> View。嗯,这时候我建议用 typealias 给它起个短名:
typealias ViewBuilder = (Context, String, Int) -> View
2.3 Lambda 表达式——最常用的函数字面量
Lambda 就是一段可以传递的代码块。语法很简单:{ 参数 -> 函数体 }。
val sum: (Int, Int) -> Int = { x, y -> x + y }
// 调用
println(sum(3, 5)) // 输出 8
你想想看,如果没有 Lambda,你得先定义一个函数,再传引用,多麻烦。Lambda 让代码变得特别紧凑。
小技巧:如果 Lambda 只有一个参数,可以用 it 代替。比如 { it.length } 等价于 { str -> str.length }。我个人习惯在参数简单时用 it,复杂时还是显式命名更清晰。
2.4 匿名函数——另一种写法
Lambda 虽然简洁,但有时候你想显式声明返回类型,或者有多个 return 点,这时候匿名函数就派上用场了。
// Lambda 写法
val list = listOf(1, 2, 3, 4, 5)
val evens = list.filter { it % 2 == 0 }
// 匿名函数写法
val evens2 = list.filter(fun(x: Int): Boolean {
return x % 2 == 0
})
说实话,我平时 90% 的情况都用 Lambda。匿名函数用得少,但遇到复杂逻辑时它能让代码意图更明确。
2.5 高阶函数作为参数
这是高阶函数最经典的应用场景。比如 Android 里常见的点击监听:
fun setOnClickListener(listener: (View) -> Unit) {
// ...
}
// 调用时
button.setOnClickListener { view ->
Toast.makeText(this, "点击了", Toast.LENGTH_SHORT).show()
}
我曾经在项目里封装过一个网络请求工具,用高阶函数处理成功和失败回调:
fun requestData(
onSuccess: (Data) -> Unit,
onError: (Throwable) -> Unit
) {
// 网络请求...
if (success) {
onSuccess(data)
} else {
onError(exception)
}
}
// 调用
requestData(
onSuccess = { data -> showData(data) },
onError = { error -> showError(error) }
)
2.6 高阶函数作为返回值
函数还能返回函数?没错。这在实现策略模式或者工厂模式时特别有用。
fun getCalculator(type: String): (Int, Int) -> Int {
return when (type) {
"add" -> { a, b -> a + b }
"sub" -> { a, b -> a - b }
"mul" -> { a, b -> a * b }
else -> { _, _ -> 0 }
}
}
// 使用
val addFunc = getCalculator("add")
println(addFunc(10, 5)) // 输出 15
我记得有一次做计算器功能,用这种方式把运算逻辑抽离出来,后续加新运算只需要在 when 分支里加一行,维护起来特别爽。
2.7 内联函数(inline)——性能优化利器
高阶函数虽然好用,但有个隐藏问题:每次传 Lambda 都会创建匿名内部类对象。如果频繁调用,GC 压力会变大。
inline 关键字就是来解决这个问题的。它会把函数调用处的代码直接“复制粘贴”过去,避免对象创建。
inline fun measureTime(block: () -> Unit) {
val start = System.currentTimeMillis()
block()
val end = System.currentTimeMillis()
println("耗时:${end - start}ms")
}
// 调用
measureTime {
// 一些耗时操作
Thread.sleep(100)
}
加了 inline 后,编译器会把 measureTime 的代码和 Lambda 的代码直接展开到调用处,不会生成额外的 Function 对象。
注意:inline 不是银弹。如果 Lambda 很大,或者函数被多次调用,内联会导致代码膨胀。我一般只在工具类函数、或者高频调用的场景用 inline。
2.8 noinline 与 crossinline
这两个是 inline 的补充。先说 noinline:当你 inline 一个函数时,所有 Lambda 参数默认都会被内联。但如果你想把某个 Lambda 当作对象传递(比如存到集合里),就需要用 noinline 标记它。
inline fun test(
block1: () -> Unit, // 会被内联
noinline block2: () -> Unit // 保持为对象
) {
block1()
val list = listOf(block2) // noinline 才能这样用
}
再说 crossinline:它用于标记 Lambda 不能有非局部 return。什么意思呢?就是 Lambda 里不能直接 return 外层函数。
inline fun runInRunnable(crossinline block: () -> Unit) {
Runnable {
// 这里不能直接 return,因为 Runnable 的上下文不同
block() // 如果 block 里有 return,编译会报错
}.run()
}
我曾经踩过这个坑:在 Lambda 里写了个 return,结果编译报错,查了半天才发现是 crossinline 的限制。嗯,后来就记住了——如果 Lambda 要在另一个上下文中执行,就用 crossinline。
2.9 知识体系总览
下面这张图帮你理清本章的核心脉络:
2.10 避坑指南
最后分享几个我踩过的坑:
- Lambda 中的 return 问题:在 Lambda 里写 return,默认会返回外层函数(如果函数是 inline 的)。想只返回 Lambda 本身,要用
return@label语法。 - 内联大函数:别把几百行的函数加上 inline,生成的字节码会爆炸。我一般只 inline 10 行以内的工具函数。
- noinline 与 crossinline 混用:如果一个 Lambda 既要被存储(noinline),又要在其他线程执行(crossinline),两个标记都要加。
总结一下:高阶函数让 Kotlin 的函数式编程变得优雅。理解函数类型、Lambda、内联机制,你就能写出更简洁、更灵活的 Android 代码。下一章我们会用这些知识来构建 DSL,那才是真正展现 Kotlin 魅力的地方。