Kotlin Lambda 表达式完全指南
Lambda 表达式是 Kotlin 函数式编程的核心特性之一,它让代码更简洁、表达力更强。无论是集合操作、协程、还是 Jetpack Compose 中的 UI 回调,都大量使用 lambda。本文将系统讲解 Kotlin lambda 的语法形式、含义、各种语法糖以及背后的原理,帮助你彻底掌握这一重要特性。
一、什么是 Lambda 表达式?
Lambda 表达式本质上是一个可以作为值传递的代码块 。它类似于匿名函数,但语法更简洁。在 Kotlin 中,lambda 是函数类型的实例,可以被赋值给变量、作为参数传递给函数或从函数返回。
// 一个简单的 lambda 表达式:接受 Int 参数,返回 Int
val square: (Int) -> Int = { x: Int -> x * x }
等价于定义了一个函数 fun square(x: Int): Int = x * x,但 lambda 是表达式,可以随处使用。
二、Lambda 表达式的语法形式
2.1 完整形式
{ 参数列表 -> 函数体 }
-
参数列表:用逗号分隔,可显式声明类型。
-
箭头
->分隔参数和函数体。 -
函数体:可以是一个表达式(自动作为返回值),也可以是多个语句(最后一条语句的值作为返回值)。
val sum = { x: Int, y: Int -> x + y } // 表达式体
val max = { x: Int, y: Int ->
if (x > y) x else y // 语句块,最后一句的值作为返回值
}
2.2 参数类型推断
如果 lambda 用于某个已知函数类型的上下文,参数类型可以省略,编译器会自动推断。
val sum: (Int, Int) -> Int = { x, y -> x + y } // 参数类型自动推断
2.3 单参数:it 隐式名称
当 lambda 只有一个参数时,可以省略参数声明,使用默认名称 it。
val square: (Int) -> Int = { it * it }
2.4 下划线 _ 表示未使用的参数
如果 lambda 有多个参数但不需要使用某个参数,可以用下划线 _ 代替参数名,避免编译器警告。
map.forEach { key, value -> println("$key -> $value") } // 正常使用
map.forEach { _, value -> println("value: $value") } // 忽略 key
三、Lambda 作为函数类型
在 Kotlin 中,函数类型用 (参数类型) -> 返回类型 表示。
val add: (Int, Int) -> Int = { a, b -> a + b }
val greeting: () -> String = { "Hello" }
val action: () -> Unit = { println("执行") }
函数类型可以作为变量类型、函数参数类型、返回值类型。
四、高阶函数与 Lambda
高阶函数:接收函数作为参数,或返回函数。
4.1 将 Lambda 作为参数传递
fun repeat(times: Int, action: (Int) -> Unit) {
for (i in 0 until times) action(i)
}
// 调用
repeat(3) { index -> println("第 $index 次") }
// 或使用 it
repeat(3) { println("第 $it 次") }
这段代码展示了 Kotlin 中 高阶函数 和 尾随 lambda 的典型用法。我们逐步拆解:
-
repeat函数定义fun repeat(times: Int, action: (Int) -> Unit) {
for (i in 0 until times) action(i)
}
repeat是一个高阶函数(因为它接收一个函数作为参数)。- 参数
times:Int类型,表示循环次数。 - 参数
action:函数类型(Int) -> Unit,表示一个接受Int参数并返回Unit(即无返回值)的函数。 - 函数体 :
for (i in 0 until times)循环times次,每次将当前索引i传递给action并执行。
简单来说,这个函数的作用是:执行action多次,每次传入循环索引。
-
调用方式一:显式命名参数
repeat(3) { index -> println("第 $index 次") }
-
由于
repeat的最后一个参数是函数类型,Kotlin 允许将 lambda 写在括号外面,这就是 尾随 lambda 语法。 -
完整写法可以是
repeat(3, { index -> println(...) }),但尾随写法更简洁。 -
index ->是 lambda 的参数声明,表示接收一个Int参数,在 lambda 内部名为index。 -
当循环执行时,
action(i)传入当前的i,lambda 中的index就接收到该值,然后执行println。
执行流程: -
repeat(3, ...)→for (i in 0 until 3)循环三次,i分别为 0、1、2。 -
每次
action(i)调用 lambda,index依次为 0、1、2,输出三行:第 0 次 第 1 次 第 2 次
-
调用方式二:使用
it隐式参数repeat(3) { println("第 $it 次") }
- 当 lambda 只有一个参数时,可以省略参数声明,使用默认名称
it指代该参数。 - 这里
it就是传入的循环索引,等价于上面的index。 - 这是一种语法糖,让代码更简洁。
执行结果与上一种完全相同。
- 尾随 lambda 的规则
- 如果函数的最后一个参数是函数类型,可以将对应的 lambda 写在括号外面。
- 如果函数只有一个参数且该参数是 lambda ,甚至可以省略括号,直接写
repeat { ... }(但这里repeat有两个参数,第一个是times,所以不能省略括号)。
- 类型推断
- 在调用
repeat(3) { index -> ... }时,编译器知道action的类型是(Int) -> Unit,因此可以推断出index的类型是Int,无需显式标注。
- 内联与性能
Kotlin 标准库中的 repeat 实际上是 inline 的,以避免 lambda 创建对象带来的开销。但上面我们自定义的 repeat 没有 inline 修饰符,所以在每次调用时都会创建 action 的函数对象。不过对于学习理解语法,这并不影响。
- 总结
- 高阶函数:将函数作为参数或返回值。
- 尾随 lambda:简化函数调用,使代码更像控制结构。
it:单参数 lambda 的隐式参数名,简化写法。- 循环与函数 :
repeat将循环逻辑与具体操作分离,体现了函数式编程的灵活。
这段代码展示了 Kotlin 如何通过简洁的语法将"做什么"(循环)和"怎么做"(具体打印)分离,是函数式编程思想的一个典型例子。
4.2 返回 Lambda
fun multiplier(factor: Int): (Int) -> Int = { it * factor }
val triple = multiplier(3)
println(triple(10)) // 30
代码解析: 这段代码展示了 Kotlin 中高阶函数 和闭包的典型用法。我们逐步分解:
-
函数定义
fun multiplier(factor: Int): (Int) -> Int = { it * factor }
multiplier是一个普通函数 ,它接收一个Int类型的参数factor。- 它的返回类型 是
(Int) -> Int,这是一个函数类型 ,表示"接收一个Int参数并返回Int的函数"。 - 函数体是一个 lambda 表达式
{ it * factor },这个 lambda 被直接作为返回值。
- lambda 的含义
{ it * factor } 是简写形式,完整写法应该是 { x: Int -> x * factor }。因为 lambda 只有一个参数,所以可以使用默认参数名 it。
factor是multiplier的参数,它在 lambda 内部被捕获(闭包)。it是 lambda 自己的参数,在调用 lambda 时由调用者传入。
-
调用
multiplierval triple = multiplier(3)
- 调用
multiplier(3),此时factor被赋值为3。 - 函数返回的 lambda 等价于:
{ it * 3 }(或{ x -> x * 3 })。 - 这个 lambda 被赋值给变量
triple。现在triple是一个函数,类型为(Int) -> Int。
-
调用
tripleprintln(triple(10)) // 输出 30
triple(10)执行 lambda,将10作为参数传递给 lambda 内部的it。- 计算
it * factor,即10 * 3= 30。
- 关键点总结
factor:是multiplier函数的参数,在调用multiplier(3)时被固定为3,并被 lambda 捕获(闭包捕获外部变量)。it:是 lambda 自己的参数,当调用返回的 lambda 时,传入的值会作为it。在triple(10)中,it就是10。
因此,it不是固定的数值,而是取决于调用时传入的参数。而factor是在创建 lambda 时就已经确定的值(这里是3)。
这种"函数返回函数"的模式在 Kotlin 中很常见,可以用于创建函数工厂 ,比如这里的multiplier可以生成不同倍数的乘法函数。
五、尾随 Lambda 语法(Trailing Lambda)
当函数的最后一个参数是函数类型时,可以将 lambda 写在括号外面,使代码更简洁。
// 标准写法
repeat(3, { println(it) })
// 尾随 lambda 写法
repeat(3) { println(it) }
如果函数只有一个参数且是 lambda,可以省略括号。
thread { println("运行") } // thread 函数接受一个 () -> Unit 参数
六、Lambda 中的返回
6.1 隐式返回
Lambda 中的最后一个表达式自动作为返回值,无需 return 关键字。
val sum = { a: Int, b: Int -> a + b } // 返回 a + b
6.2 显式返回(使用标签)
如果需要在 lambda 中提前返回,需要使用标签 ,因为 return 默认返回包含它的最近函数(而非 lambda)。
fun processList(list: List<Int>) {
list.forEach {
if (it == 0) return // 这会从 processList 返回!
println(it)
}
println("结束")
}
上面的代码遇到 0 会直接退出 processList,而不是仅退出 lambda。要只退出 lambda,使用标签:
list.forEach label@{
if (it == 0) return@label
println(it)
}
或者使用隐式标签:lambda 名称或函数名:
list.forEach {
if (it == 0) return@forEach
println(it)
}
6.3 匿名函数中的返回
如果使用匿名函数 替代 lambda,则 return 行为不同(返回匿名函数本身)。但匿名函数语法稍冗长:
list.forEach(fun(value) {
if (value == 0) return // 只返回匿名函数,不影响外部
println(value)
})
七、闭包:访问外部变量
Lambda 可以访问其外部作用域中的变量(包括局部变量和成员变量)。与 Java 不同,Kotlin 允许修改外部变量。
var count = 0
val increment = { count++ } // 可以修改外部变量
increment()
println(count) // 1
这依赖于 Kotlin 对变量的引用捕获,编译器会生成适当的包装类(对于可变变量)。
八、内联函数与 Lambda 性能
高阶函数如果直接使用 lambda,可能会创建额外的对象(函数对象)。Kotlin 通过 内联函数 避免这种开销。用 inline 修饰的函数,在调用处会展开函数体,从而消除 lambda 对象创建。
inline fun repeat(times: Int, action: (Int) -> Unit) {
for (i in 0 until times) action(i)
}
标准库中许多高阶函数(如 forEach、map、filter)都是内联的,以确保性能。
8.1 noinline 和 crossinline
noinline:禁止内联某个 lambda 参数(例如需要传递或存储时)。crossinline:表示 lambda 不允许非局部返回,防止 lambda 中的return影响外部函数。
九、Lambda 与集合操作
Kotlin 集合的许多函数都接受 lambda,使代码高度简洁。
val list = listOf(1, 2, 3, 4, 5)
// 过滤
val evens = list.filter { it % 2 == 0 }
// 映射
val squares = list.map { it * it }
// 归约
val sum = list.reduce { acc, i -> acc + i }
// 查找
val firstEven = list.find { it % 2 == 0 }
十、常见陷阱与注意事项
- return 的作用域:默认返回包含 lambda 的函数,而不是 lambda 本身。
- 捕获可变变量:虽然 Kotlin 允许,但要注意线程安全。
- 内存泄漏 :如果 lambda 捕获了生命周期较长的引用(如 Activity),可能导致内存泄漏。在 Android 开发中,使用
viewLifecycleOwner或remember等方式避免。 - 重载解析:当多个函数签名类似时,可能需要显式指定参数类型。
十一、总结
| 特性 | 描述 |
|---|---|
| Lambda 表达式 | { 参数 -> 表达式 },可作为值传递 |
| 类型推断 | 上下文已知时可省略参数类型 |
it |
单参数默认名称 |
| 尾随 lambda | 最后一个参数为 lambda 时可写在括号外 |
| 闭包 | 可访问并修改外部变量 |
| 返回 | 隐式返回最后一行;非局部返回需用标签或匿名函数 |
| 内联 | inline 消除 lambda 对象开销 |
| 集合操作 | 大量使用 lambda,实现函数式风格 |
Lambda 是 Kotlin 函数式编程的基石,熟练掌握它能让代码更简洁、更具表达力。在 Android 开发中,无论是 Compose 的回调、协程的挂起函数,还是数据处理的链式调用,都离不开 lambda。希望本文能帮助你全面掌握这一重要语法特性。