Kotlin 函数式编程
🧮 Kotlin 函数式编程与高阶函数
Kotlin 是一门融合了面向对象与函数式编程的语言。掌握高阶函数、Lambda 与集合操作符,是写出简洁、优雅、不易出错代码的关键,也是现代 Android 开发(Compose、协程、Flow)的地基。
一、函数也是一等公民
在 Kotlin 中,函数可以被赋值给变量、作为参数传递、作为返回值返回——这就是"函数一等公民"。
// 函数引用(可调用引用)
fun isEven(x: Int): Boolean = x % 2 == 0
val predicate: (Int) -> Boolean = ::isEven // 函数引用赋值给变量
// 函数作为参数
fun filter(list: List<Int>, pred: (Int) -> Boolean): List<Int> =
list.filter(pred)
// 函数作为返回值
fun makeAdder(x: Int): (Int) -> Int = { y -> x + y }
fun main() {
val add5 = makeAdder(5)
println(add5(3)) // 8
println(filter(listOf(1, 2, 3, 4), ::isEven)) // [2, 4]
}函数类型语法
// (参数类型...) -> 返回类型
val f1: (Int, Int) -> Int = { a, b -> a + b }
val f2: () -> Unit = { println("hello") }
val f3: (String) -> Unit = { println(it) } // 单参数可用 it| 写法 | 含义 |
|---|---|
(Int, Int) -> Int | 两个 Int 参数,返回 Int |
() -> Unit | 无参数无返回值 |
(String) -> Unit | 一个参数,it 是默认参数名 |
(Int) -> ((Int) -> Int) | 返回函数的函数(柯里化) |
二、Lambda 表达式
2.1 语法要点
// 完整写法
val sum = { a: Int, b: Int -> a + b }
// 简化:参数类型可推断时省略
val sum: (Int, Int) -> Int = { a, b -> a + b }
// 唯一参数用 it
val printIt: (String) -> Unit = { println(it) }
// 无参数
val greet = { println("hi") }2.2 尾随 Lambda(Trailing Lambda)
当 Lambda 是函数最后一个参数时,可以移出括号:
// 标准库经典用法
listOf(1, 2, 3).map { it * 2 } // 等价于 .map({ it * 2 })
listOf(1, 2, 3).fold(0) { acc, x -> acc + x }
// 自定义函数
fun <T> List<T>.myForEach(action: (T) -> Unit) {
for (item in this) action(item)
}
listOf("a", "b").myForEach { println(it) }2.3 匿名函数
val f = fun(x: Int): Int = x * 2 // 匿名函数,可显式声明返回类型💡 与 Lambda 的区别:匿名函数可以显式指定返回类型;Lambda 的
return返回外层函数(见下文"非局部返回")。
三、集合操作符全家桶
函数式编程在集合处理上威力最大:
3.1 转换类
val list = listOf(1, 2, 3, 4, 5)
list.map { it * 2 } // [2, 4, 6, 8, 10] 一一映射
list.flatMap { listOf(it, it * 10) } // 拍平再映射 [1,10,2,20,...]
list.groupBy { it % 2 } // {0=[2,4], 1=[1,3,5]} 分组
list.associateWith { it * it } // {1=1, 2=4, ...} 以元素为键
list.zip(listOf("a","b","c")) // [(1,a),(2,b),(3,c)] 配对
list.mapIndexed { i, v -> "$i:$v" } // 带索引3.2 过滤类
list.filter { it % 2 == 0 } // [2, 4]
list.filterNot { it % 2 == 0 } // [1, 3, 5]
list.filterNotNull() // 过滤 null
list.take(3) // 前 3 个
list.drop(2) // 去掉前 2 个
list.distinct() // 去重
list.single { it > 4 } // 恰好一个满足,否则抛异常3.3 聚合类
list.sum() // 15
list.reduce { acc, x -> acc + x } // 15(首元素作为初值)
list.fold(10) { acc, x -> acc + x } // 25(指定初值 10)
list.count { it > 3 } // 2
list.maxOrNull() // 5
list.minOrNull() // 1
list.average() // 3.03.4 顺序与条件
list.sorted() // 升序
list.sortedBy { -it } // 按属性排序
list.any { it > 4 } // true
list.all { it > 0 } // true
list.none { it > 10 } // true
list.firstOrNull { it > 3 } // 4
list.elementAtOrNull(99) // null(安全取值)3.5 惰性序列 asSequence()
大量数据时避免中间集合创建:
// 问题:每个操作符都会创建新 List(Eager)
listOf(1, 2, ..., 1000000)
.map { it * 2 } // 创建 100w 元素的中间集合
.filter { it % 3 == 0 }// 又创建 100w 元素
// 优化:Sequence 惰性求值(Lazy)
listOf(1, 2, ..., 1000000)
.asSequence()
.map { it * 2 }
.filter { it % 3 == 0 }
.toList() // 只在最终 toList 时一次性执行| 场景 | 集合操作符 | Sequence |
|---|---|---|
| 数据量小 | ✅ 简单直观 | 不必要 |
| 数据量大 | ❌ 中间集合浪费内存 | ✅ 惰性省内存 |
| 需要短路 | ❌ 全量处理 | ✅ take(3) 提前终止 |
| 多次遍历 | 每次新建 | 单次遍历 |
四、作用域函数
let / run / with / apply / also 是 Kotlin 最常用的内联作用域函数:
| 函数 | 引用方式 | 返回值 | 典型场景 |
|---|---|---|---|
let | it | Lambda 结果 | 非空判断 + 转换 |
run | this | Lambda 结果 | 配置对象并返回结果 |
with | this | Lambda 结果 | 对同一对象多次操作 |
apply | this | 对象本身 | 初始化/配置对象 |
also | it | 对象本身 | 副作用(日志、校验) |
// let:安全调用 + 转换
val length = name?.let { it.length } ?: 0
// apply:配置对象(Builder 风格)
val dialog = AlertDialog.Builder(context).apply {
setTitle("提示")
setMessage("确定删除?")
setPositiveButton("确定") { _, _ -> }
}.create()
// also:调试日志
val user = User("tom").also {
Log.d("TAG", "created: $it")
}
// with:同一对象多次操作
val result = with(builder) {
add(1)
add(2)
build()
}📖 进阶阅读:Kotlin 基础语法详解、Kotlin 协程从入门到进阶
五、非局部返回与标签
Lambda 中的 return 默认返回外层函数(非局部返回),因为 Lambda 被内联:
fun findFirst(list: List<Int>): Int {
list.forEach {
if (it == 3) return it // 直接返回外层函数 findFirst!
}
return -1
}
// 不想返回外层函数时用标签
fun test(list: List<Int>) {
list.forEach lit@{
if (it == 3) return@lit // 只跳过本次循环
println(it)
}
println("done") // 会执行
}注意:只有 inline 函数(如 forEach、map)才支持非局部返回;非内联函数会编译报错。
六、内联函数与性能
高阶函数创建 Lambda 会产生匿名内部类或 Function 对象,内联可消除该开销:
// 未内联:每次调用都创建 Function 对象
fun repeat(n: Int, action: () -> Unit) { for (i in 0 until n) action() }
// 内联:编译期展开,无对象创建
inline fun repeatInline(n: Int, action: () -> Unit) { for (i in 0 until n) action() }flowchart LR
A[调用高阶函数] --> B{是否 inline}
B -->|否| C[创建 Function 对象<br>+ 虚调用]
B -->|是| D[编译期展开函数体<br>直接内嵌执行]
C --> E[性能开销]
D --> F[零开销]| 特性 | 普通函数 | inline 函数 |
|---|---|---|
| 运行时开销 | 有(对象创建) | 无(编译期展开) |
| 非局部返回 | 不支持 | 支持 |
| 代码体积 | 小 | 增大(每个调用点复制) |
| 适用场景 | 常规 | 高频调用 + Lambda 参数 |
noinline 与 crossinline
inline fun f(a: () -> Unit, noinline b: () -> Unit) {
// a 内联展开;b 保持函数对象(可被存储/传递)
}
inline fun g(crossinline h: () -> Unit) {
// 禁止 h 内非局部返回(如在内部对象/协程中使用)
object : Runnable { override fun run() = h() }
}七、reified 泛型
内联函数配合 reified 可在运行时获取泛型类型:
// 普通泛型:类型被擦除,无法 T::class
// inline + reified:编译期替换为真实类型
inline fun <reified T> Gson.fromJson(json: String): T =
fromJson(json, T::class.java)
// 使用
val user: User = gson.fromJson("""{"name":"tom"}""")💡 Gson/Moshi 的扩展函数、协程的
launch、Compose 的remember都大量使用 reified。
八、高频面试题
Q1:let、run、with、apply、also 有什么区别?
查看答案
核心区别在两点:① Lambda 内是 it 还是 this;② 返回值是 Lambda 结果还是对象本身。
| 函数 | 接收者 | 返回值 |
|---|---|---|
let | it | Lambda 结果 |
run | this | Lambda 结果 |
with | this | Lambda 结果 |
apply | this | 对象本身 |
also | it | 对象本身 |
记忆口诀:有 also/apply 返回自己,有 with/run 用 this,let 用 it。apply 用于初始化配置,let 用于空安全 + 转换,also 用于副作用。
Q2:map 和 flatMap 的区别?
查看答案
map 对每个元素做一一映射,结果个数不变;flatMap 先对每个元素映射为一个集合,再拍平所有集合,结果个数可能变多。例如 listOf(1,2).flatMap { listOf(it, it*10) } 得到 [1,10,2,20]。flatMap 常用于一对多关系(如一个用户的多张订单)。
Q3:集合操作符和 Sequence 有什么区别?什么时候用 Sequence?
查看答案
集合操作符是立即求值的,每个操作符都会产生新的中间集合,数据量大时浪费内存;Sequence 是惰性求值的,操作符只是记录"处理步骤",直到 toList() 等终端操作才一次性处理,且支持 take 提前短路。当数据量很大(如大文件逐行处理)、操作符链较长、或需要提前终止时使用 Sequence。
Q4:什么是非局部返回?哪些函数支持?
查看答案
Lambda 内的 return 默认返回包含它的外层函数,这就是非局部返回。只有 inline 函数(编译期把 Lambda 展开到调用点)才支持非局部返回;普通函数参数里的 Lambda 会报错。如果不想返回外层函数,可以用标签 return@forEach,或使用 crossinline 禁止非局部返回。
Q5:inline 关键字的作用和代价?
查看答案
作用:① 消除 Lambda 创建 Function 对象的运行时开销(编译期展开);② 支持非局部返回;③ 支持 reified 具体化泛型。代价:每个调用点都会复制函数体,代码体积增大;且内联后不能再访问函数内部私有成员。因此只对"参数含 Lambda 且高频调用"的函数使用 inline。
小结
- Kotlin 函数是一等公民:可赋值、可传参、可返回
- Lambda 支持尾随语法、
it简写、标签返回 - 集合操作符分转换/过滤/聚合/条件四类,大数据用 Sequence 惰性求值
- 五个作用域函数按"接收者 + 返回值"两个维度记忆
inline消除 Lambda 开销、支持非局部返回与reified泛型
📖 进阶阅读:Kotlin 泛型详解 | Kotlin 扩展函数 | Kotlin 委托机制与内联函数