Kotlin Lambda 与高阶函数
Lambda 和高阶函数是 Kotlin 函数式编程的基石。Kotlin 把 lambda 设计得极简洁——单参数隐式 it、最后一个参数可写在括号外、标准库所有集合操作都用 lambda。配合 inline 函数,这些 lambda 几乎没有运行时开销。本篇系统讲。
1. Lambda 表达式语法
Lambda 是匿名函数——没有名字的一段代码。用 { 参数 -> 函数体 } 写:
// Lambda 表达式:用 {} 包裹的一段代码
// 完整语法:{ 参数列表 -> 函数体 }
val sum = { a: Int, b: Int -> a + b }
println(sum(3, 4)) // 7
// 类型推断
val mul: (Int, Int) -> Int = { a, b -> a * b }
println(mul(3, 4)) // 12
// 无参数 lambda
val greet: () -> String = { "你好!" }
println(greet())
// 单参数:用 it 隐式参数(省略参数列表)
val square: (Int) -> Int = { it * it }
println(square(5)) // 25
// 多语句 lambda:最后一行是返回值
val complex = { x: Int ->
val y = x * 2
val z = y + 1
z // 返回值(不是赋值语句)
}
println(complex(5)) // 11几个要点:
- 函数类型:
(Int, Int) -> Int表示接收两个 Int 返回 Int 的函数。 - 单参数隐式 it:只有一个参数时,可省略
参数 ->,用it引用。 - 最后一条表达式是返回值:不需要
return,lambda 末尾的表达式自动是返回值。 - 多语句:lambda 体可以多行,最后一行是返回值。
2. 高阶函数(接收函数作为参数)
高阶函数是接收函数作为参数或返回函数的函数。Kotlin 标准库大量使用高阶函数:
// 高阶函数:接收函数作为参数 或 返回函数
fun calc(a: Int, b: Int, op: (Int, Int) -> Int): Int {
return op(a, b)
}
// 用 lambda 传函数
val r1 = calc(10, 3) { x, y -> x + y } // 13
val r2 = calc(10, 3) { x, y -> x * y } // 30
val r3 = calc(10, 3) { x, y -> x - y } // 7
// lambda 在【括号外】(如果它是最后一个参数)
val r4 = calc(10, 3) { x, y -> x / y } // 3
// 函数引用 ::,把命名函数当参数传
fun add(a: Int, b: Int) = a + b
fun sub(a: Int, b: Int) = a - b
val r5 = calc(10, 3, ::add) // 13
val r6 = calc(10, 3, ::sub) // 7关键语法:如果 lambda 是函数的最后一个参数,可以写在括号外面。这让 DSL 风格代码非常优雅:list.map { it * 2 }.filter { it > 5 }。
3. 返回函数 + 闭包
函数也可以返回函数——这是函数式编程的核心特性:
// 高阶函数返回函数
fun makeMultiplier(factor: Int): (Int) -> Int {
return { n -> n * factor } // 捕获 factor
}
val double = makeMultiplier(2)
val triple = makeMultiplier(3)
println(double(5)) // 10
println(triple(5)) // 15
// 工厂模式用函数式写法
fun validator(pattern: String): (String) -> Boolean {
val regex = Regex(pattern)
return { input -> regex.matches(input) }
}
val isEmail = validator("^[\\w.-]+@[\\w.-]+\\.\\w+$")
println(isEmail("a@b.com")) // true
println(isEmail("invalid")) // false
// 闭包:lambda 捕获外部变量
fun counter(): () -> Int {
var n = 0
return { n++; n }
}
val c = counter()
println(c()) // 1
println(c()) // 2
println(c()) // 3闭包(Closure):lambda 捕获外层作用域的变量。counter() 返回的 lambda 捕获了 n,每次调用都修改它。这种"有状态的函数"是函数式编程的常见模式。
4. inline 内联函数(消除 lambda 开销)
普通 lambda 在 JVM 上编译为 Function<T> 对象,有装箱和对象创建开销。Kotlin 用 inline 关键字把函数体和 lambda 体在编译期插入调用点,消除所有开销:
// inline 函数:消除 lambda 的运行时开销
// 普通高阶函数:lambda 会编译成 Function 对象,有装箱和对象开销
// inline 函数:编译器把函数体和 lambda 体【直接插入】调用点
inline fun repeat(times: Int, action: (Int) -> Unit) {
for (i in 0 until times) action(i)
}
repeat(3) { i -> println("第 $i 次") }
// 编译后等价于:
// for (i in 0 until 3) println("第 $i 次")
// 没有 lambda 对象创建,性能等同手写循环
// 标准库的 forEach / map / filter / let / run / apply 都是 inline
// 所以 Kotlin 用 lambda 写集合操作【几乎无运行时开销】
// noinline:阻止某个 lambda 被内联
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
inlined()
notInlined()
}
// crossinline:用于"非直接调用"场景
inline fun runAsync(crossinline action: () -> Unit) {
Thread { action() }.start()
}因为标准库的 forEach、map、filter、let、run、apply 全部 inline,所以用 lambda 写集合操作几乎无运行时开销——性能等同手写循环。这是 Kotlin 设计的精妙之处。
另外两个相关关键字:noinline(禁止某个 lambda 被内联)、crossinline(用于"非直接调用"场景,如线程内)。
5. 五大作用域函数(let / run / with / apply / also)
这是 Kotlin 标准库提供的最常用的五个高阶函数,Android 开发里几乎处处可见。区别在于引用对象的方式和返回值:
// 5 个作用域函数(scope functions):let / run / with / apply / also
// 区别:
// - 引用对象用 it 还是 this
// - 返回值是对象本身 还是 lambda 结果
data class User(var name: String, var age: Int)
// 1. let:【it】+ 返回 lambda 结果
val len = "hello".let { it.length } // 5
// 常用于非空判断:
val name: String? = "小明"
name?.let { println(it.length) } // 只在非空时执行
// 2. run:【this】+ 返回 lambda 结果
val greeting = "hello".run {
println(this) // hello
uppercase()
}
println(greeting) // HELLO
// 3. with:不是方法,是顶层函数【this】+ 返回 lambda 结果
val sb = StringBuilder()
val result = with(sb) {
append("a")
append("b")
append("c")
toString()
}
println(result) // abc
// 4. apply:【this】+ 返回对象本身(用于配置/构建)
val user = User("小明", 20).apply {
age = 21 // this.age = 21
name = "Xiao Ming"
}
println(user) // User(name=Xiao Ming, age=21)
// 5. also:【it】+ 返回对象本身(用于副作用)
val list = mutableListOf(1, 2, 3).also {
println("初始化: $it")
}
// 初始化: [1, 2, 3]选择指南:
- let:配合可空调用
x?.let { ... },在非空上下文执行块。返回 lambda 结果。 - run:像 let 但用
this引用。返回 lambda 结果。 - with:不是方法(顶层函数),用于"对一个对象做多个操作"。返回 lambda 结果。
- apply:用于配置/构建对象(返回对象本身)。链式配置。
- also:用于副作用(返回对象本身)。调试日志、链式插入。
记忆口诀:let/run/with 返回结果,apply/also 返回对象本身;let/also 用 it,run/with/apply 用 this。
6. 集合的函数式操作
Lambda + 集合是 Kotlin 日常编程的核心。集合 API 几乎全部基于高阶函数:
// 集合的函数式操作
val nums = listOf(1, 2, 3, 4, 5, 6)
// forEach:遍历(无返回值)
nums.forEach { println(it) }
// map:一对一转换
val doubled = nums.map { it * 2 } // [2, 4, 6, 8, 10, 12]
// filter:过滤
val evens = nums.filter { it % 2 == 0 } // [2, 4, 6]
// 链式调用
val result = nums
.filter { it > 1 }
.map { it * it }
.sortedDescending()
.take(3)
println(result) // [36, 25, 16]
// 分组与统计
val byParity = nums.groupBy { if (it % 2 == 0) "偶" else "奇" }
println(byParity) // {奇=[1, 3, 5], 偶=[2, 4, 6]}
// reduce / fold:累加
val sum = nums.reduce { acc, n -> acc + n } // 21
val product = nums.fold(1) { acc, n -> acc * n } // 720
// forEachIndexed:带索引
nums.forEachIndexed { i, v -> println("[$i] = $v") }链式调用是函数式风格的精髓——读起来像"做什么"而非"怎么做"。多个操作可以无缝组合,且因为 inline,性能等同手写循环。
7. 尾递归 lambda(Fold/Reduce)
fold / reduce 是处理集合的"累加"利器,任何需要"扫描集合产生单个值"的场景都适用:
// reduce:无初始值,从第一个元素开始
val max = listOf(3, 1, 4, 1, 5, 9).reduce { acc, n -> if (acc > n) acc else n }
println(max) // 9
// fold:有初始值
val concatenated = listOf("a", "b", "c").fold("") { acc, s -> acc + s }
println(concatenated) // abc
// 计算 list 的平均值
val stats = listOf(80, 90, 75, 95).let { nums ->
val sum = nums.sum()
val avg = sum.toDouble() / nums.size
val min = nums.minOrNull()
val max = nums.maxOrNull()
Triple(avg, min, max)
}
println(stats) // (85.0, 75, 95)
// 用 runningFold 生成中间结果(滑动累加)
val prefix = listOf(1, 2, 3, 4).runningFold(0) { acc, n -> acc + n }
println(prefix) // [0, 1, 3, 6, 10]8. 实战:解析配置文件
fun parseConfig(text: String): Map<String, String> {
return text
.lineSequence() // 按行
.map { it.trim() } // 去空白
.filter { it.isNotEmpty() && !it.startsWith("#") } // 去空行和注释
.map { it.split("=", limit = 2) } // 分割
.filter { it.size == 2 } // 只保留 key=value
.associate { it[0].trim() to it[1].trim() } // 转 Map
}
fun main() {
val config = parseConfig("""
# 服务器配置
host = localhost
port = 8080
debug = true
# 数据库
db_url = jdbc:postgres://localhost/mydb
""".trimIndent())
config.forEach { (k, v) -> println("$k => $v") }
}
// host => localhost
// port => 8080
// debug => true
// db_url => jdbc:postgres://localhost/mydb这个例子完美展示函数式风格:一行流水线完成了"读、清洗、过滤、转换、聚合"。没有 for 循环、没有可变变量、每一步都是声明式的。这种代码易读、易测、易维护。
9. DSL 构建器(高阶函数的进阶用法)
Lambda + 高阶函数让 Kotlin 可以写出非常优雅的 DSL(领域特定语言)。比如 HTML 构建:
fun html(init: HtmlBuilder.() -> Unit): String {
val builder = HtmlBuilder()
builder.init()
return builder.build()
}
class HtmlBuilder {
private val content = StringBuilder()
fun body(init: () -> String) {
content.append("<body>").append(init()).append("</body>")
}
fun h1(text: String) {
content.append("<h1>$text</h1>")
}
fun p(text: String) {
content.append("<p>$text</p>")
}
fun build() = "<html>$content</html>"
}
val page = html {
body {
h1("标题")
p("正文内容")
"完成"
}
}
println(page)
// <html><body><h1>标题</h1><p>正文内容</p>完成</body></html>这种"带接收者的 lambda"(T.() -> R)是 Kotlin DSL 的核心机制——Gradle 的 Kotlin DSL、Ktor 的路由、Anko、Jetpack Compose 全基于此。
小结
这一章你掌握了 Kotlin 函数式编程的全部基础:Lambda 语法、高阶函数、函数引用、闭包、inline 内联、五大作用域函数(let/run/with/apply/also)、集合函数式操作、DSL 构建。下一篇进入 Kotlin 最强大的异步特性——协程。
← 上一篇 Kotlin 接口
下一篇 Kotlin 协程 →