Kotlin 匿名函数与 Lambda 实战心法
目标:不是“知道概念”,而是站在写业务代码和做架构设计的角度,理解:
这些语法解决什么实际问题、在什么场景下用、用好之后代码会怎样变“短、稳、好维护”。
一、什么是匿名函数:为什么需要“没有名字的函数”?
-
概念(通俗版)
没有名字的函数,就是“匿名函数”。
平时我们写:- 有名字:
fun add(a: Int, b: Int): Int - 匿名:
fun(a: Int, b: Int): Int { ... }(没有函数名)
- 有名字:
-
为什么要学 / 有什么用?
- 当这段逻辑只在一个地方临时用一下时,没必要为它起名、到处找定义。
- 结合集合操作(
map/filter)或回调(网络请求回调、点击事件)时,匿名函数可以就近编写逻辑,阅读成本更低。
-
示例:把逻辑就地写清楚
// 有名字的函数
fun add(a: Int, b: Int): Int {
return a + b
}
// 匿名函数,存到变量里
val addAnon = fun(a: Int, b: Int): Int {
return a + b
}
fun main() {
println(addAnon(1, 2)) // 3
}
二、Lambda:实际项目里最常见的“函数写法”
-
概念(通俗版)
Lambda 是一种更简短的“匿名函数”写法,可以当成“函数的字面量”(像123、"abc"那样的字面量)。 -
为什么要学 / 有什么用?
- Kotlin 生态(特别是集合 API、协程、Flow、Compose、Android 回调)大量使用 Lambda。
- 不会 Lambda,几乎看不懂现代 Kotlin 代码;会了 Lambda,可以写出声明式、可组合的代码(例如:一眼看出“先过滤、再排序、再映射”)。
-
基本形式
// 函数类型:(参数列表) -> 返回类型
// Lambda:{ 参数列表 -> 函数体 }
val addLambda: (Int, Int) -> Int = { a, b ->
a + b
}
val sayHi: () -> Unit = {
println("Hi")
}
三、函数类型 与 隐式返回:为“可组合能力”打地基
1. 函数类型(Function Type)
-
概念(通俗版)
在 Kotlin 里,函数本身也可以作为一种“类型”,像Int、String一样:(Int) -> String:接收一个Int,返回一个String的函数() -> Unit:不接收参数、不返回值的函数(Unit类似 Java 里的void)
-
为什么要学 / 有什么用?
- 一旦你能“把函数当成数据传来传去”,就可以搭建出强可组合的业务框架:
- 统一的重试策略、埋点包装、鉴权包装、统一异常处理等,都可以写成“包函数的函数”。
- 这也是理解协程、Flow、Compose、路由中间件等高级用法的基础。
- 一旦你能“把函数当成数据传来传去”,就可以搭建出强可组合的业务框架:
-
示例
// f 的类型是:接收两个 Int,返回 Int 的函数
val f: (Int, Int) -> Int = { x, y -> x + y }
fun main() {
println(f(3, 4)) // 7
}
2. Lambda 的隐式返回
-
隐式返回(通俗版)
对于 Lambda,最后一行表达式 的值,就是返回值,一般不写return。 -
实战价值
- 单行 Lambda 用隐式返回,可以让集合链式调用更“像一条业务描述”,减少噪音:
orders.filter { it.paid }.sumOf { it.amount }
- 但在复杂 Lambda 中,适当拆成多个语句、加临时变量,会更利于调试。
- 单行 Lambda 用隐式返回,可以让集合链式调用更“像一条业务描述”,减少噪音:
// 匿名函数写法(显式 return)
val add1 = fun(a: Int, b: Int): Int {
return a + b
}
// Lambda 写法(隐式返回)
val add2: (Int, Int) -> Int = { a, b ->
a + b // 这行的结果就是返回值
}
四、函数参数 与 it:在“可读性”和“简洁”间做取舍
1. 普通参数写法
val square: (Int) -> Int = { x ->
x * x
}
2. it 关键字(单参数简写)
-
概念(通俗版)
当 Lambda 只有一个参数时,可以省略参数名和->,Kotlin 会自动给你一个名字:it。
it就代表“传进来的那个唯一参数”。 -
实战建议
- 简单场景(1–2 个字段计算)用
it,很紧凑:users.map { it.name }
- 复杂场景建议自己起名,避免“一屏幕全是 it”看不懂:
orders.filter { order -> order.status == PAID && order.amount > 100 }
- 简单场景(1–2 个字段计算)用
// 完整写法
val square1: (Int) -> Int = { x -> x * x }
// 使用 it 简写
val square2: (Int) -> Int = { it * it }
val list = listOf(1, 2, 3)
val doubled = list.map { it * 2 } // 等价于 list.map { x -> x * 2 }
五、匿名函数的类型推断:既省字数,又不牺牲可读性
- 类型推断(通俗版)
Kotlin 会根据上下文自动推断 Lambda 的参数类型和返回类型,你不必到处写类型。
val nums = listOf(1, 2, 3)
// map 期望的是 (Int) -> Int
val result = nums.map { it * 10 }
// 等价于:nums.map { x: Int -> x * 10 }
- 一般规律与实战取舍:
- 能准确推断时省略类型,让表达式更紧凑;
- 边界接口/公共 API 中要保留类型(特别是公开函数签名),提高自说明性;
- 调试复杂泛型时,暂时把类型写全,有利于理解编译器在做什么。
六、定义“参数是函数”的函数(高阶函数):抽取可复用的“流程模板”
-
高阶函数(通俗版)
接收函数作为参数,或返回一个函数的函数,叫“高阶函数”。
可以理解为:函数可以把函数当参数/当返回值来用。 -
为什么要学 / 有什么用?
- 把“流程”抽出来,把“细节”交给 Lambda,让业务代码像拼乐高一样可组合:
- 网络请求统一做:显示 Loading → 请求 → 兜底错误提示;
- 数据库操作统一做:开启事务 → 执行具体逻辑 → 提交/回滚。
- 把“流程”抽出来,把“细节”交给 Lambda,让业务代码像拼乐高一样可组合:
1. 参数是函数的函数:把“变化点”外包出去
// operation 是一个函数:(Int, Int) -> Int
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
fun main() {
// 传入 Lambda
val sum = calculate(3, 5) { x, y -> x + y }
val mul = calculate(3, 5) { x, y -> x * y }
println(sum) // 8
println(mul) // 15
}
2. Lambda 作为最后一个参数的简略写法:DSL 味道的基础
-
规则(通俗版)
当 Lambda 是函数的最后一个参数时,可以把{ ... }拿到括号外。
如果函数只有一个 Lambda 参数,甚至可以省略圆括号。 -
为什么重要?
- 这是 Kotlin 各种“小 DSL”(领域特定语言)的基础,比如:
apply { }、run { }、also { }等标准库函数;Room配置、Gradle Kotlin DSL、Ktor 路由、Jetpack Compose UI DSL。
- 这是 Kotlin 各种“小 DSL”(领域特定语言)的基础,比如:
// 原始写法
calculate(3, 5, { x, y -> x + y })
// 最后一个参数是 Lambda,可写成:
calculate(3, 5) { x, y -> x + y }
fun runTask(task: () -> Unit) {
task()
}
// 只有一个 Lambda 参数时:
runTask {
println("running...")
}
七、函数内联(inline function):写“语法糖”的性能安全网
-
概念(通俗版)
inline是编译器优化关键字,意思是:“把函数的代码拷贝到调用处”,而不是在运行时真正去调用。
这样可以减少高阶函数和 Lambda 带来的额外开销(如对象创建、函数调用栈)。 -
为什么要学 / 有什么用?
- 当你大量使用高阶函数来封装“包装逻辑”(埋点、日志、锁、事务)时,频繁的 Lambda 分配可能带来性能问题;
inline让你可以放心大胆写优雅的高阶函数封装,而不用太担心小 Lambda 的开销。
inline fun measureTime(block: () -> Unit) {
val start = System.currentTimeMillis()
block()
val end = System.currentTimeMillis()
println("cost: ${end - start} ms")
}
fun main() {
measureTime {
Thread.sleep(100)
}
}
- 记忆:
inline更像是“给语法糖兜底的优化开关”,对 API 设计层面非常重要。
八、函数引用(Function Reference):把已有函数无缝接入新框架
-
概念(通俗版)
用::可以拿到“函数本身”,而不是调用结果。
就像“把函数当成一个值传着玩”。 -
为什么要学 / 有什么用?
- 当你已经有一堆老函数,但现在要接入一个需要
(T) -> R的新 API 时,函数引用可以免改老函数、直接接进去; - 在路由表、事件总线、校验器注册等场景,函数引用可以让映射表更直观。
- 当你已经有一堆老函数,但现在要接入一个需要
fun isEven(x: Int): Boolean = x % 2 == 0
fun main() {
val nums = listOf(1, 2, 3, 4)
// 直接传函数引用
val evens = nums.filter(::isEven)
println(evens) // [2, 4]
}
- 对比 Lambda 写法:
nums.filter { x -> isEven(x) }
nums.filter(::isEven) // 更简洁
九、函数类型作为返回类型:构建“可配置的行为”
-
概念(通俗版)
函数不仅可以接收函数参数,还可以返回一个函数,常用于策略、工厂等模式。 -
价值点
- 可以根据配置、环境(测试 / 线上)、权限等,动态生成不同策略函数;
- 复用一整套“行为组合”,常见于:价格策略、限流策略、重试策略等。
// 返回一个 (Int) -> Int 的函数
fun createAdder(n: Int): (Int) -> Int {
return { x -> x + n }
}
fun main() {
val add5 = createAdder(5) // 得到“加 5 的函数”
println(add5(10)) // 15
val add10 = createAdder(10)
println(add10(10)) // 20
}
这里的 Lambda 使用了外部的
n,就是“闭包”。
十、闭包(Closure):让函数“带着状态”跑
-
概念(通俗版)
闭包 = 函数 + 它记住的外部变量。
Lambda / 匿名函数可以“带着”它创建时环境里的变量一起存活和工作。 -
为什么要学 / 有什么用?
- 你可以不引入复杂的类,直接用闭包构造轻量级带状态的逻辑单元;
- 与协程、Flow 配合时,闭包是“把外部上下文带入异步环境”的关键。
fun makeCounter(): () -> Int {
var count = 0 // 外部变量,被 Lambda 捕获
return {
count++ // 使用并修改外部变量
count
}
}
fun main() {
val counter1 = makeCounter()
println(counter1()) // 1
println(counter1()) // 2
println(counter1()) // 3
val counter2 = makeCounter()
println(counter2()) // 1 // 新的闭包,有自己的 count
}
- 通俗理解:
- 每次调用
makeCounter(),就生成了一个“自带私有存储空间的函数对象”,这个私有空间里的变量不会被外部直接访问,但会一直存在。
十一、Lambda 与匿名内部类:现代 Kotlin 写法的“代沟”
在 Android 等场景最常见,比如
setOnClickListener。
1. 匿名内部类(Anonymous Inner Class)
- 概念(通俗版)
临时创建一个没有名字的类,只用一次,多用于实现接口或抽象类。
button.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
println("clicked")
}
})
2. Lambda 写法(SAM 转换)
- SAM(Single Abstract Method):只有一个抽象方法的接口。
Kotlin 允许对这种接口使用 Lambda 的简化写法。
// 接口只有一个抽象方法时:
button.setOnClickListener { view ->
println("clicked: $view")
}
// 单参数时也可用 it
button.setOnClickListener {
println("clicked: $it")
}
3. 本质差异(简化理解)
- 匿名内部类
- 本质是新建了一个没有名字的类 + 一个实例。
this指向的是那个匿名类的实例。
- Lambda
- 更偏“函数字面量”,编译器会做优化,比如用于 SAM 接口时减少额外类。
- Lambda 里
this一般指向外部类实例。
4. 实战建议
- 能用 Lambda 的地方优先用 Lambda,代码更短、更清晰;
- 旧代码中大量匿名内部类,一般可以安全地重构为 Lambda(单方法接口);
- 接口有多个抽象方法时,才必须用匿名内部类。

242

被折叠的 条评论
为什么被折叠?



