Lambda 与高阶函数
源:High-order functions and lambdas
在 Kotlin 中,函数是“一等公民”。这意味着函数可以存储在变量中、作为参数传递给其他函数,或者作为其他函数的返回值。这种特性是函数式编程(FP)的基石。
函数类型 (Function Types)
要像变量一样处理函数,首先需要描述它的“类型”。函数类型由参数列表和返回值类型组成。
kotlin
// 格式: (参数类型列表) -> 返回值类型
val onClick: (Int) -> Unit = ...
val comparator: (String, String) -> Int = ...
val provider: () -> Any = ...可空函数类型
函数类型本身也可以是可空的,但需要注意括号的位置。
kotlin
// 变量本身可空(即 onClick 可能是 null)
val onClick: ((Int) -> Unit)? = null
// 这是一个返回 Int? 的函数,变量本身不可空
val measure: () -> Int? = ...Lambda 表达式
Lambda 表达式是实例化函数类型最简洁的方式。它本质上是一个匿名的小段代码块。
语法解剖
kotlin
val sum: (Int, Int) -> Int = { x: Int, y: Int ->
println("Calculating $x + $y")
x + y // 最后一行为返回值
}- 大括号包裹:Lambda 始终在
{}中。 - 参数声明:在
->之前。如果类型可推断,可省略类型标注。 - 函数体:在
->之后。 - 隐式返回:Lambda 的最后一个表达式自动成为返回值(严禁使用
return关键字返回结果)。
简写规则
Kotlin 提供了极极致的语法糖来简化 Lambda 调用:
kotlin
// 如果 Lambda 只有一个参数,可省略声明,直接用 it 代表
list.filter { it > 0 }
// 等价于: list.filter { x -> x > 0 }kotlin
// 如果函数的最后一个参数是 Lambda,可将其移出括号
thread(start = true) {
doWork()
}kotlin
// 如果不使用某个参数,用 _ 代替,避免命名污染
map.forEach { (_, value) ->
println(value)
}kotlin
// 直接拆解复合对象(如 Pair)
map.mapValues { (key, value) ->
"$key-$value"
}匿名函数 (Anonymous Functions)
虽然 Lambda 很方便,但它有两个局限:
- 无法显式指定返回值类型(只能靠推断)。
- 控制流 (
return) 行为特殊。
当需要更精细的控制时,可以使用匿名函数:
kotlin
val filter = fun(x: Int): Boolean {
if (x == 0) return false // 这里的 return 是局部的
return x > 0
}Lambda vs 匿名函数:Return 的区别
- Lambda:
return是非局部返回(Non-local return),它会直接退出包含该 Lambda 的外部函数(除非 Lambda 是内联的)。 - 匿名函数:
return是局部返回,仅退出该匿名函数本身,类似于普通的函数调用。
高阶函数 (Higher-Order Functions)
高阶函数是指接受函数作为参数或返回函数的函数。这是 Kotlin 标准库(如 filter, map)的核心实现方式。
kotlin
// 接受一个函数作为参数
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body() // 执行传入的函数
} finally {
lock.unlock()
}
}带接收者的函数类型 (Function Types with Receiver) 核心难点
这是理解 Kotlin DSL 和作用域函数(apply, with)的关键。
Kotlin 允许定义一种特殊的函数类型,它在调用时需要一个接收者对象 (Receiver)。
语法对比
- 普通函数类型:
(A, B) -> C - 带接收者的类型:
A.(B) -> C(读作:在 A 上调用,参数为 B,返回 C)
实例化与调用
这种 Lambda 内部拥有一个隐式的 this,指向接收者对象。
kotlin
// 定义:这个 Lambda 必须在 String 上执行
val shout: String.() -> Unit = {
// 这里的 this 是 String 实例
println(this.uppercase() + "!!!")
}
// 调用方式 1:扩展调用语法(推荐)
"hello".shout()
// 调用方式 2:作为第一个参数传递
shout("hello")为什么这很重要?
当你看到 html { body { ... } } 这种代码时,body 之所以能直接调用,就是因为 lambda 的类型是 HTML.() -> Unit,当前的上下文( this)被切换成了 HTML 对象。
闭包与变量捕获
Lambda 可以访问其闭包(定义时的外部作用域)中的变量。与 Java 不同,Kotlin 的 Lambda 可以修改捕获的非 final 变量。
kotlin
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it // 直接修改外部变量,无需 Wrapper
}总结
- 类型系统:
(Int) -> String是合法的类型。 - 语法糖:熟练使用
it、尾随 Lambda 和解构。 - 接收者:
String.() -> Unit让 Lambda 内部拥有了this,这是 DSL 的魔法之源。 - 控制流:注意 Lambda 中
return的非局部性。