这是一份纯 Kotlin 视角的高阶函数学习文档,适合系统入门和反复查阅。
内容覆盖:
- 高阶函数的定义
- 函数类型的写法
- Lambda 表达式
- 匿名函数
- 函数引用
- 闭包
- 内联函数
- 作用域函数
- 集合与序列中的高阶函数
- 协程与 Flow 中常见的高阶函数
- 常见误区与练习
1. 什么是高阶函数
高阶函数指的是满足以下任意条件的函数:
- 接收函数作为参数
- 返回一个函数
例如:
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
fun main() {
val result = calculate(3, 4) { x, y -> x + y }
println(result) // 7
}
这里的 calculate 就是高阶函数,因为它接收了一个函数参数 operation。
2. 为什么要学高阶函数
高阶函数是 Kotlin 非常核心的能力之一,因为它能让代码:
- 更抽象
- 更简洁
- 更容易复用
- 更接近“表达意图”
例如,遍历列表时,你不一定需要自己写 for 循环:
val result = listOf(1, 2, 3, 4)
.filter { it > 2 }
.map { it * 10 }
这里的重点不是“怎么手动一项项处理”,而是“我要过滤”和“我要转换”。
3. 函数类型基础
在 Kotlin 中,函数也有类型。
3.1 最常见的函数类型
(Int, Int) -> Int
含义:
- 接收两个
Int - 返回一个
Int
再例如:
() -> Unit
含义:
- 不接收参数
- 不返回结果
再例如:
(String) -> Boolean
含义:
- 接收一个
String - 返回一个
Boolean
3.2 声明函数类型变量
val add: (Int, Int) -> Int = { a, b -> a + b }
3.3 作为函数参数
fun execute(action: () -> Unit) {
action()
}
3.4 作为函数返回值
fun createPrinter(): (String) -> Unit {
return { text -> println(text) }
}
4. Lambda 表达式
Lambda 是 Kotlin 中最常见的函数字面量写法。
基本结构:
{ 参数 -> 函数体 }
4.1 最简单的例子
val square: (Int) -> Int = { x -> x * x }
4.2 多参数 Lambda
val add: (Int, Int) -> Int = { a, b -> a + b }
4.3 没有参数的 Lambda
val sayHello: () -> Unit = { println("Hello") }
4.4 最后一行是返回值
val multiply: (Int, Int) -> Int = { a, b ->
println("计算中")
a * b
}
这里最后一行 a * b 就是返回值。
5. it 的含义
当 lambda 只有一个参数时,可以省略参数名,直接使用默认参数名 it。
val lengths = listOf("a", "ab", "abc").map { it.length }
等价于:
val lengths = listOf("a", "ab", "abc").map { text -> text.length }
所以:
it只是默认参数名- 不是特殊关键字语义
- 只是为了简化书写
6. 尾随 Lambda
如果函数最后一个参数是 lambda,就可以把它写到括号外面。
例如:
list.filter({ it > 0 })
通常会写成:
list.filter { it > 0 }
这是 Kotlin 非常常见的风格。
再比如:
run({
println("running")
})
可以写成:
run {
println("running")
}
7. 高阶函数的两种核心形态
7.1 接收函数参数
fun repeatAction(times: Int, action: (Int) -> Unit) {
for (i in 0 until times) {
action(i)
}
}
调用:
repeatAction(3) {
println("第 $it 次")
}
7.2 返回函数
fun createAdder(base: Int): (Int) -> Int {
return { value -> base + value }
}
fun main() {
val add10 = createAdder(10)
println(add10(5)) // 15
}
8. 匿名函数
除了 lambda,还可以使用匿名函数。
val add = fun(a: Int, b: Int): Int {
return a + b
}
Lambda 和匿名函数都能表示“没有名字的函数”,但匿名函数更接近普通函数语法。
8.1 什么时候用匿名函数
适合:
- 逻辑较复杂
- 想写显式
return - 不想混淆 lambda 返回规则
9. 函数引用
如果一个函数已经存在,可以直接把它的引用传出去。
9.1 普通函数引用
fun isEven(x: Int): Boolean = x % 2 == 0
val result = listOf(1, 2, 3, 4).filter(::isEven)
9.2 成员引用
val lengths = listOf("a", "ab", "abc").map(String::length)
9.3 构造函数引用
class User(val name: String)
val creator: (String) -> User = ::User
函数引用的好处:
- 更简洁
- 可复用已有函数
- 某些场景下比 lambda 更清晰
10. 闭包 Closure
Lambda 可以访问它定义时所在作用域中的变量,这种能力叫闭包。
fun main() {
var sum = 0
listOf(1, 2, 3).forEach {
sum += it
}
println(sum) // 6
}
这里 lambda 修改了外部变量 sum。
闭包很强大,但也要注意:
- 如果频繁修改外部可变状态,代码可能会变得难以维护
11. 常见集合高阶函数
集合是高阶函数最经典的应用场景。
11.1 forEach
对每个元素执行操作。
listOf(1, 2, 3).forEach {
println(it)
}
11.2 map
把每个元素转换成新元素。
val doubled = listOf(1, 2, 3).map { it * 2 }
11.3 mapIndexed
转换时拿到下标。
val result = listOf("a", "b").mapIndexed { index, value ->
"$index:$value"
}
11.4 filter
保留符合条件的元素。
val result = listOf(1, 2, 3, 4).filter { it % 2 == 0 }
11.5 filterNot
去掉符合条件的元素。
val result = listOf(1, 2, 3, 4).filterNot { it % 2 == 0 }
11.6 any
判断是否至少有一个元素满足条件。
val hasEven = listOf(1, 3, 4).any { it % 2 == 0 }
11.7 all
判断是否全部满足条件。
val allPositive = listOf(1, 2, 3).all { it > 0 }
11.8 none
判断是否没有元素满足条件。
val noneNegative = listOf(1, 2, 3).none { it < 0 }
11.9 count
统计满足条件的元素数量。
val count = listOf(1, 2, 3, 4, 5).count { it % 2 == 0 }
println(count) // 2
11.9 firstOrNull
获取第一个符合条件的元素,找不到返回 null。
val firstEven = listOf(1, 3, 4, 6).firstOrNull { it % 2 == 0 }
11.10 find
find 和 firstOrNull 很接近。
val firstBig = listOf(1, 10, 20).find { it > 5 }
11.11 associateBy
把列表变成 Map。
data class User(val id: Int, val name: String)
val users = listOf(User(1, "Tom"), User(2, "Jack"))
val userMap = users.associateBy { it.id }
11.12 groupBy
按某个规则分组。
val grouped = listOf("apple", "ape", "banana").groupBy { it.first() }
11.13 fold
累积结果。
val sum = listOf(1, 2, 3, 4).fold(0) { acc, item ->
acc + item
}
11.14 reduce
和 fold 类似,但没有初始值。
val sum = listOf(1, 2, 3).reduce { acc, item ->
acc + item
}
11.15 flatMap
映射后拍平。
val result = listOf("a,b", "c,d").flatMap { it.split(",") }
11.16 distinct
去重,保留第一次出现的元素顺序。
val result = listOf(1, 2, 2, 3, 1).distinct()
println(result) // [1, 2, 3]
11.17 distinctBy
按某个字段去重。
data class User(val id: Int, val name: String)
val users = listOf(
User(1, "Tom"),
User(1, "Tom Duplicate"),
User(2, "Jack")
)
val result = users.distinctBy { it.id }
println(result)
11.18 sortedByDescending
按某个规则降序排序。
val result = listOf(1, 5, 2, 4).sortedByDescending { it }
println(result) // [5, 4, 2, 1]
11.19 partition
按条件一分为二,返回一个 Pair。
val (even, odd) = listOf(1, 2, 3, 4, 5).partition { it % 2 == 0 }
println(even) // [2, 4]
println(odd) // [1, 3, 5]
11.20 zip
把两个集合按下标配对。
val names = listOf("Tom", "Jack")
val ages = listOf(18, 20)
val result = names.zip(ages)
println(result) // [(Tom, 18), (Jack, 20)]
11.21 associate
把集合转换成 Map,键值都由你自己决定。
val result = listOf("a", "bb", "ccc").associate { it to it.length }
println(result) // {a=1, bb=2, ccc=3}
11.22 sumOf
按规则求和。
data class Product(val name: String, val price: Int)
val total = listOf(
Product("A", 10),
Product("B", 20)
).sumOf { it.price }
println(total) // 30
11.23 mapNotNull
先映射,再自动去掉 null。
val result = listOf("1", "2", "x", "3").mapNotNull { it.toIntOrNull() }
println(result) // [1, 2, 3]
11.24 filterNotNull
过滤掉 null 元素。
val result = listOf(1, null, 2, null, 3).filterNotNull()
println(result) // [1, 2, 3]
11.25 sortedWith
使用自定义比较器排序。
data class User(val name: String, val age: Int)
val users = listOf(
User("Tom", 20),
User("Jack", 18),
User("Lucy", 20)
)
val result = users.sortedWith(compareBy<User> { it.age }.thenBy { it.name })
println(result)
11.26 chunked
把集合按指定大小分块。
val result = listOf(1, 2, 3, 4, 5, 6, 7).chunked(3)
println(result) // [[1, 2, 3], [4, 5, 6], [7]]
11.27 windowed
按滑动窗口取子列表。
val result = listOf(1, 2, 3, 4, 5).windowed(size = 3, step = 1)
println(result) // [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
11.28 runningFold
返回每一步累计过程。
val result = listOf(1, 2, 3).runningFold(0) { acc, item -> acc + item }
println(result) // [0, 1, 3, 6]
11.29 runningReduce
返回每一步 reduce 的累计过程。
val result = listOf(1, 2, 3, 4).runningReduce { acc, item -> acc + item }
println(result) // [1, 3, 6, 10]
11.30 groupingBy 与 eachCount
按规则分组并统计每组数量。
val result = listOf("apple", "ape", "banana", "book")
.groupingBy { it.first() }
.eachCount()
println(result) // {a=2, b=2}
12. fold 深入理解
fold 是理解高阶函数非常好的例子。
val result = listOf(1, 2, 3).fold(10) { acc, item ->
acc + item
}
执行过程:
- 初始值是
10 - 第一次:
10 + 1 = 11 - 第二次:
11 + 2 = 13 - 第三次:
13 + 3 = 16
所以结果是 16。
这里:
fold负责控制“遍历和累计”的流程- lambda 负责决定“怎么累计”
13. Sequence 与惰性计算
当链式调用很多、数据量很大时,可以使用 Sequence。
val result = listOf(1, 2, 3, 4, 5)
.asSequence()
.filter { it > 2 }
.map { it * 2 }
.take(2)
.toList()
特点:
- 惰性执行
- 中间结果不会马上全部创建
- 适合长链条操作
14. 作用域函数
作用域函数本质上也是高阶函数,因为它们都接收 lambda。
14.1 let
val length = "kotlin".let {
println(it)
it.length
}
特点:
- 对象以
it形式传入 - 返回 lambda 最后一行结果
14.2 run
val result = "kotlin".run {
uppercase()
}
特点:
- 对象以
this形式使用 - 返回 lambda 最后一行结果
14.3 apply
val list = mutableListOf<Int>().apply {
add(1)
add(2)
}
特点:
- 对象以
this形式使用 - 返回对象本身
14.4 also
val text = "hello".also {
println("原始值: $it")
}
特点:
- 对象以
it形式传入 - 返回对象本身
14.5 with
val result = with(StringBuilder()) {
append("Hello ")
append("Kotlin")
toString()
}
14.6 takeIf
如果对象满足条件,就返回对象本身;否则返回 null。
val result = 10.takeIf { it > 5 }
println(result) // 10
val result2 = 3.takeIf { it > 5 }
println(result2) // null
非常适合做“条件保留”:
val text = "Kotlin"
val longText = text.takeIf { it.length > 5 }
14.7 takeUnless
和 takeIf 相反。
如果条件不成立,就返回对象本身;如果条件成立,返回 null。
val result = 3.takeUnless { it > 5 }
println(result) // 3
15. let、run、apply、also 怎么选
一个简单记法:
- 想返回“结果值”:
let/run - 想返回“对象本身”:
apply/also - 想用
it:let/also - 想用
this:run/apply
16. 接收者函数类型
普通函数类型:
(String) -> Int
接收者函数类型:
String.() -> Int
它表示“一个以 String 作为接收者的函数”。
例如:
val countA: String.() -> Int = {
count { it == 'a' }
}
fun main() {
println("banana".countA()) // 3
}
这种形式在 DSL 设计中非常常见。
17. 常见字符串函数
虽然这些不全是高阶函数,但它们经常和高阶函数链式配合使用。
17.1 isBlank
判断字符串是否为空或者只包含空白字符。
println(" ".isBlank()) // true
println("abc".isBlank()) // false
17.2 isNotBlank
判断字符串是否不是空白字符串。
println("hello".isNotBlank()) // true
println(" ".isNotBlank()) // false
这个函数经常和 filter 搭配:
val result = listOf("a", " ", "", "kotlin").filter { it.isNotBlank() }
println(result) // [a, kotlin]
17.3 isEmpty
只判断长度是否为 0,不关心是不是空格。
println("".isEmpty()) // true
println(" ".isEmpty()) // false
17.4 isNotEmpty
println("abc".isNotEmpty()) // true
17.5 substringBefore
取指定分隔符前面的内容。
val result = "name=Tom".substringBefore("=")
println(result) // name
17.6 substringAfter
取指定分隔符后面的内容。
val result = "name=Tom".substringAfter("=")
println(result) // Tom
17.7 trim
去掉首尾空白字符。
println(" Kotlin ".trim()) // Kotlin
17.8 uppercase / lowercase
println("kotlin".uppercase()) // KOTLIN
println("KOTLIN".lowercase()) // kotlin
17.9 startsWith / endsWith
println("kotlin".startsWith("kot")) // true
println("kotlin".endsWith("lin")) // true
17.10 contains
println("kotlin".contains("lin")) // true
18. 常见数值与范围函数
这些函数也非常高频,尤其在处理边界值时。
18.1 coerceIn
把值限制在某个范围内。
println(5.coerceIn(1, 10)) // 5
println(0.coerceIn(1, 10)) // 1
println(20.coerceIn(1, 10)) // 10
含义:
- 小于最小值,就返回最小值
- 大于最大值,就返回最大值
- 在范围内,就返回原值
18.2 coerceAtLeast
保证值至少是多少。
println(3.coerceAtLeast(5)) // 5
println(8.coerceAtLeast(5)) // 8
18.3 coerceAtMost
保证值至多是多少。
println(12.coerceAtMost(10)) // 10
println(6.coerceAtMost(10)) // 6
18.4 in
判断值是否在范围内。
println(5 in 1..10) // true
println(20 in 1..10) // false
18.5 until
创建左闭右开区间。
for (i in 0 until 5) {
print(i)
}
// 01234
19. 常见空安全与空值处理函数
19.1 ?.
安全调用。
val length = text?.length
19.2 ?:
Elvis 操作符,空值时给默认值。
val name = input ?: "Unknown"
19.3 requireNotNull
如果为空就抛异常,否则返回非空值。
val text: String? = "hello"
val result = requireNotNull(text)
println(result.length)
19.4 checkNotNull
和 requireNotNull 类似,但语义偏内部状态检查。
val value: String? = "abc"
val result = checkNotNull(value)
println(result)
19.5 orEmpty
空字符串或空集合常用兜底方法。
val text: String? = null
println(text.orEmpty()) // ""
对于集合:
val list: List<Int>? = null
println(list.orEmpty()) // []
20. 常见标准库工具函数
20.1 run
执行一段代码并返回结果。
val result = run {
val a = 10
val b = 20
a + b
}
println(result) // 30
20.2 repeat
重复执行若干次。
repeat(3) {
println("index = $it")
}
20.3 lazy
延迟初始化。
val text by lazy {
println("初始化")
"Hello"
}
20.4 buildList
构建不可变列表。
val result = buildList {
add(1)
add(2)
add(3)
}
20.5 buildMap
构建不可变 Map。
val result = buildMap {
put("a", 1)
put("b", 2)
}
20.6 buildSet
构建不可变 Set。
val result = buildSet {
add(1)
add(2)
add(2)
}
println(result) // [1, 2]
20.7 runCatching
捕获异常并返回 Result。
val result = runCatching {
"123".toInt()
}
println(result.getOrNull()) // 123
如果失败:
val result = runCatching {
"abc".toInt()
}
println(result.getOrNull()) // null
20.8 getOrNull
从 Result 里安全取值,失败时返回 null。
val result = runCatching { "100".toInt() }
println(result.getOrNull()) // 100
20.9 getOrElse
失败时给默认值。
val result = runCatching { "abc".toInt() }
.getOrElse { -1 }
println(result) // -1
20.10 getOrDefault
失败时返回指定默认值。
val result = runCatching { "abc".toInt() }
.getOrDefault(0)
println(result) // 0
20.11 onSuccess
成功时执行附加逻辑。
runCatching { "123".toInt() }
.onSuccess { println("成功: $it") }
20.12 onFailure
失败时执行附加逻辑。
runCatching { "abc".toInt() }
.onFailure { println("失败: ${it.message}") }
20.13 use
对实现了 Closeable 的对象自动关闭资源。
import java.io.File
val text = File("demo.txt").bufferedReader().use { reader ->
reader.readText()
}
use 很重要,因为它能确保资源在使用后自动关闭。
21. 返回规则与 return
Lambda 的最后一行默认作为返回值。
val plusOne: (Int) -> Int = { it + 1 }
但 return 在 lambda 中有时比较特殊。
17.1 标签返回
fun main() {
listOf(1, 2, 3).forEach {
if (it == 2) return@forEach
println(it)
}
}
这里 return@forEach 表示只退出本次 lambda,不退出外层函数。
17.2 非局部返回
在某些 inline 场景中,lambda 里的 return 可能直接返回外层函数。
fun test() {
listOf(1, 2, 3).forEach {
if (it == 2) return
}
println("不会执行")
}
22. inline 内联函数
高阶函数很方便,但有时会带来额外开销。inline 可以让编译器把函数体展开到调用处。
inline fun measure(block: () -> Unit) {
val start = System.currentTimeMillis()
block()
println(System.currentTimeMillis() - start)
}
18.1 为什么需要 inline
- 减少函数对象创建
- 减少调用开销
- 支持某些非局部返回能力
18.2 不要滥用
如果函数很大,过度内联会让字节码膨胀。
23. noinline 与 crossinline
19.1 noinline
表示某个 lambda 参数不要被内联。
inline fun doSomething(
inlined: () -> Unit,
noinline normal: () -> Unit
) {
inlined()
normal()
}
19.2 crossinline
表示允许内联,但不允许非局部返回。
inline fun runTask(crossinline action: () -> Unit) {
val runnable = Runnable {
action()
}
runnable.run()
}
24. SAM 转换
如果一个 Java 接口只有一个抽象方法,Kotlin 可以直接用 lambda 替代它。
例如 Runnable:
val runnable = Runnable {
println("running")
}
这叫 SAM 转换。
它在和 Java API 交互时非常常见。
25. 协程中的高阶函数
协程 API 本身就大量使用高阶函数。
21.1 launch
scope.launch {
println("执行协程任务")
}
launch 接收一个 lambda,这个 lambda 会在协程中执行。
21.2 async
val deferred = scope.async {
10 + 20
}
println(deferred.await()) // 30
21.3 withContext
val result = withContext(Dispatchers.Default) {
(1..1000).sum()
}
21.4 coroutineScope
suspend fun loadAll() = coroutineScope {
val a = async { 10 }
val b = async { 20 }
a.await() + b.await()
}
25.5 suspendCoroutine
把回调风格 API 包装成挂起函数时很常用。
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
suspend fun loadValue(): Int = suspendCoroutine { continuation ->
val resultFromCallback = 42
continuation.resume(resultFromCallback)
}
这里的 resume 不是高阶函数,但它是协程学习里非常重要的函数。
含义:
- 当前挂起函数先暂停
- 等回调结果回来后,调用
resume(value) - 协程从暂停点继续执行,并把
value作为结果返回
如果失败,可以使用:
import kotlin.coroutines.resumeWithException
例如:
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
suspend fun requestData(success: Boolean): String = suspendCoroutine { continuation ->
if (success) {
continuation.resume("OK")
} else {
continuation.resumeWithException(IllegalStateException("Request failed"))
}
}
26. Flow 中的高阶函数
Flow 是 Kotlin 协程里的数据流类型,它的操作几乎全都是高阶函数。
22.1 collect
flow.collect { value ->
println(value)
}
22.2 collectLatest
flow.collectLatest { value ->
println("最新值: $value")
}
22.3 map
flow.map { it * 2 }
22.4 filter
flow.filter { it > 0 }
22.5 onEach
flow.onEach { println("观察到: $it") }
22.6 catch
flow.catch { e ->
println("异常: ${e.message}")
}
22.7 debounce
flow.debounce(300)
22.8 flatMapLatest
flow.flatMapLatest { value ->
otherFlow(value)
}
27. 其他非常常见但容易漏掉的函数
这一节把学习 Kotlin 时很常遇到的高频函数再补充一批。
27.1 first
获取第一个元素,不存在会抛异常。
val result = listOf(10, 20, 30).first()
println(result) // 10
27.2 last
获取最后一个元素。
val result = listOf(10, 20, 30).last()
println(result) // 30
27.3 single
要求集合中只有一个元素,否则抛异常。
val result = listOf(42).single()
println(result) // 42
27.4 singleOrNull
如果刚好一个元素就返回,否则返回 null。
println(listOf(42).singleOrNull()) // 42
println(listOf(1, 2).singleOrNull()) // null
27.5 joinToString
把集合拼成字符串。
val result = listOf("a", "b", "c").joinToString(", ")
println(result) // a, b, c
27.6 getOrNull
按下标取元素,越界返回 null。
val result = listOf(10, 20).getOrNull(5)
println(result) // null
27.7 ifEmpty
如果集合或字符串为空,则给出替代值。
val result = emptyList<Int>().ifEmpty { listOf(1, 2, 3) }
println(result) // [1, 2, 3]
27.8 ifBlank
如果字符串为空白,则给出替代值。
val result = " ".ifBlank { "default" }
println(result) // default
27.9 ifEmpty 用在字符串上
val result = "".ifEmpty { "fallback" }
println(result) // fallback
27.10 maxByOrNull / minByOrNull
按某个规则找最大或最小元素。
data class Student(val name: String, val score: Int)
val students = listOf(
Student("Tom", 80),
Student("Jack", 95),
Student("Lucy", 70)
)
val top = students.maxByOrNull { it.score }
println(top) // Student(name=Jack, score=95)
27.11 maxOfOrNull / minOfOrNull
直接取某个字段的最大值或最小值。
val result = listOf(10, 50, 30).maxOfOrNull { it }
println(result) // 50
27.12 compareBy
创建比较规则。
data class Person(val name: String, val age: Int)
val people = listOf(
Person("Tom", 20),
Person("Jack", 18),
Person("Lucy", 20)
)
val result = people.sortedWith(compareBy<Person> { it.age }.thenBy { it.name })
println(result)
27.13 onEach
在链式调用中插入副作用,不改变元素本身。
val result = listOf(1, 2, 3)
.onEach { println("元素: $it") }
.map { it * 2 }
println(result) // [2, 4, 6]
27.14 also
常用于调试、日志、附加动作。
val result = mutableListOf(1, 2, 3).also {
println("原始列表: $it")
}
27.15 apply
常用于对象初始化。
val sb = StringBuilder().apply {
append("Hello")
append(" Kotlin")
}
println(sb.toString())
27.16 repeat
重复执行一段代码。
repeat(3) { index ->
println("index = $index")
}
27.17 binarySearch
对有序集合做二分查找。
val list = listOf(1, 3, 5, 7, 9)
println(list.binarySearch(5)) // 2
27.18 random
随机取一个元素。
val result = listOf("a", "b", "c").random()
println(result)
27.19 shuffled
打乱集合顺序。
val result = listOf(1, 2, 3, 4, 5).shuffled()
println(result)
27.20 reversed
返回倒序集合。
val result = listOf(1, 2, 3).reversed()
println(result) // [3, 2, 1]
27.21 reversedArray
返回倒序数组。
val arr = arrayOf(1, 2, 3)
println(arr.reversedArray().joinToString())
27.22 toIntOrNull / toLongOrNull
安全转换数字,失败时返回 null。
println("123".toIntOrNull()) // 123
println("abc".toIntOrNull()) // null
27.23 toBooleanStrictOrNull
严格解析布尔值,非法字符串返回 null。
println("true".toBooleanStrictOrNull()) // true
println("yes".toBooleanStrictOrNull()) // null
27.24 contentEquals
比较数组内容是否相等。
val a = arrayOf(1, 2, 3)
val b = arrayOf(1, 2, 3)
println(a.contentEquals(b)) // true
27.25 contentDeepEquals
比较嵌套数组内容是否深度相等。
val a = arrayOf(arrayOf(1, 2), arrayOf(3, 4))
val b = arrayOf(arrayOf(1, 2), arrayOf(3, 4))
println(a.contentDeepEquals(b)) // true
28. 常用函数分类速查表
下面这张速查表更适合平时回忆。
集合处理
map:转换mapNotNull:转换并去空filter:过滤filterNotNull:过滤空值flatMap:展开distinct:去重groupBy:分组groupingBy:高级分组入口eachCount:统计每组数量associate:转 Mapzip:配对chunked:分块windowed:滑动窗口
聚合统计
sumOf:求和count:计数any:是否存在all:是否全部满足none:是否没有fold:带初始值累计reduce:不带初始值累计runningFold:保留累计过程runningReduce:保留 reduce 过程
排序与比较
sortedBy:升序排序sortedByDescending:降序排序sortedWith:自定义比较compareBy:创建比较规则maxByOrNull:按规则取最大元素minByOrNull:按规则取最小元素maxOfOrNull:取最大值minOfOrNull:取最小值
空安全
?.:安全调用?::空值默认let:非空后继续处理takeIf:满足条件才保留takeUnless:不满足条件才保留orEmpty:空值转默认空对象ifEmpty:空时替换ifBlank:空白时替换requireNotNull:判空并抛异常checkNotNull:内部状态判空
字符串
isBlank:是否空白isNotBlank:是否非空白trim:去首尾空格substringBefore:取前半部分substringAfter:取后半部分uppercase:转大写lowercase:转小写startsWith:前缀判断endsWith:后缀判断
数值与范围
coerceIn:限制范围coerceAtLeast:至少多少coerceAtMost:最多多少in:判断是否在范围中until:左闭右开区间
异常处理
runCatching:捕获异常getOrNull:失败返回 nullgetOrElse:失败时用函数兜底getOrDefault:失败时用默认值onSuccess:成功回调onFailure:失败回调
协程
launch:启动协程async:异步并返回结果withContext:切换上下文coroutineScope:结构化并发suspendCoroutine:回调转挂起resume:恢复协程resumeWithException:恢复并抛异常
29. 高阶函数与 DSL
Kotlin 能写出很多优雅 DSL,很大程度上依赖高阶函数和接收者 lambda。
例如:
class UserBuilder {
var name: String = ""
var age: Int = 0
fun build(): User = User(name, age)
}
data class User(val name: String, val age: Int)
fun buildUser(block: UserBuilder.() -> Unit): User {
val builder = UserBuilder()
builder.block()
return builder.build()
}
fun main() {
val user = buildUser {
name = "Tom"
age = 18
}
println(user)
}
30. 什么时候适合用高阶函数
适合:
- 有重复的控制流程
- 行为规则可以抽出来单独传入
- 想提高代码复用性
- 想写链式、声明式风格
例如:
- 遍历和筛选
- 事件回调
- 异步任务执行
- 构建 DSL
- 状态转换
31. 什么时候不要滥用
虽然高阶函数很强,但也不是所有地方都适合。
不建议滥用在:
- 逻辑过于复杂导致 lambda 套 lambda
- 参数太多导致阅读困难
- 闭包修改大量外部状态
- 为了“炫技巧”而让代码难懂
一个判断标准:
如果别人读起来比普通函数更费劲,那就可能写过头了。
32. 常见误区
26.1 以为所有带大括号的都是高阶函数
不是。
只有“函数接收另一个函数作为参数”时,才是高阶函数场景。
26.2 搞不清 it
it 只是单参数 lambda 的默认名字。
26.3 以为 map 会修改原集合
不会。
map 返回的是新集合。
26.4 以为 forEach 和 map 一样
不一样。
forEach:执行操作,通常返回Unitmap:转换数据,返回新集合
26.5 以为作用域函数只是语法糖
它们不只是简写,也是在统一“对象作用域中的操作方式”。
32.6 以为常用函数越多越好
不是。
函数多是为了表达更精准,不是为了炫技巧。 如果 map/filter/let/apply 写得比普通逻辑还绕,就应该收一收。
33. 记忆口诀
集合函数
map:变filter:筛forEach:做fold:累flatMap:拆开再铺平
作用域函数
let:拿it算结果run:拿this算结果apply:拿this改对象,最后还对象also:拿it做附加操作,最后还对象
高频工具函数
takeIf:满足条件才留下takeUnless:不满足条件才留下distinct:去重any:有没有至少一个all:是不是全部都满足none:是不是一个都没有coerceIn:强行压到范围里isNotBlank:不是空白字符串resume:让挂起的协程继续往下走
34. 学习顺序建议
推荐按这个顺序学:
- 普通函数和函数类型
- Lambda 基础
it和尾随 lambda- 集合高阶函数
- 作用域函数
- 匿名函数与函数引用
- 内联函数
- 协程中的高阶函数
- Flow 操作符
- DSL 与接收者 lambda
35. 综合练习
练习 1:接收函数参数
fun operate(a: Int, b: Int, op: (Int, Int) -> Int): Int {
return op(a, b)
}
尝试传入:
- 加法
- 减法
- 乘法
练习 2:返回函数
fun createMultiplier(factor: Int): (Int) -> Int {
return { value -> value * factor }
}
练习 3:集合处理
val nums = listOf(1, 2, 3, 4, 5)
val result = nums.filter { it > 2 }.map { it * it }
思考结果是什么。
练习 4:作用域函数
val text = StringBuilder().apply {
append("Hello")
append(" Kotlin")
}.toString()
练习 5:fold
val result = listOf(1, 2, 3, 4).fold(1) { acc, item ->
acc * item
}
思考结果是什么。
练习 6:takeIf
val age = 20
val adultAge = age.takeIf { it >= 18 }
println(adultAge)
思考:
- 如果
age = 15,结果是什么
练习 7:distinct
val result = listOf("a", "b", "a", "c", "b").distinct()
println(result)
练习 8:coerceIn
println((-5).coerceIn(0, 100))
println(150.coerceIn(0, 100))
println(60.coerceIn(0, 100))
练习 9:isNotBlank
val names = listOf("Tom", " ", "", "Jack")
val result = names.filter { it.isNotBlank() }
println(result)
练习 10:mapNotNull
val result = listOf("1", "x", "2", "3a", "4").mapNotNull { it.toIntOrNull() }
println(result)
练习 11:runCatching
val result = runCatching { "abc".toInt() }
println(result.getOrDefault(-1))
练习 12:groupingBy
val result = listOf("cat", "car", "dog", "door")
.groupingBy { it.first() }
.eachCount()
println(result)
练习 13:chunked
val result = (1..10).toList().chunked(4)
println(result)
练习 14:takeUnless
val text = "Kotlin"
val result = text.takeUnless { it.length < 3 }
println(result)
36. 一页总结
高阶函数的本质是:
“把行为当成值来传递。”
Kotlin 中几乎所有声明式风格都围绕它展开:
- 集合操作
- 作用域函数
- 协程
- Flow
- DSL
你只要每次都问自己两个问题,就会更容易看懂:
- 这里哪个函数在接收 lambda
- 这个 lambda 到底什么时候执行
把这两个问题想清楚,Kotlin 高阶函数就不神秘了。
37. 参考资料
以下资料可继续深入学习: