25、Kotlin标准库:标准库概览、常用扩展函数、作用域函数、集合操作
说实话,Kotlin 的标准库是我最喜欢它的原因之一。很多语言的标准库就是给你一堆基础工具,够用但谈不上好用。Kotlin 不一样,它把很多我们日常开发中高频使用的模式,直接封装成了标准库函数。你想想看,写 Java 的时候,处理一个集合要写多少样板代码?在 Kotlin 里,一行搞定。
这一章,我会带你快速过一遍标准库的核心内容。不是那种枯燥的 API 罗列,而是挑出我工作中真正高频使用的东西:扩展函数、作用域函数、集合操作。这些东西你学会了,写代码的效率能翻一倍。
标准库概览:你每天都在用,只是没意识到
Kotlin 标准库(kotlin-stdlib)是每个 Kotlin 项目默认引入的依赖。它不像 Java 那样把很多东西扔给第三方,而是内置了大量实用工具。我个人把它分成三大块:
- 核心语言扩展:比如
String、Collection、Map上的扩展函数,还有Pair、Triple这些数据容器。 - 作用域函数:
let、run、with、apply、also,这五个函数是 Kotlin 的招牌特性。 - 集合操作:
filter、map、reduce等函数式 API,让集合处理变得优雅。
嗯,这里要注意:标准库还包含 kotlinx.coroutines 这样的协程库,但那是进阶内容,我们放到后面章节讲。这一章先聚焦最基础、最常用的部分。
常用扩展函数:给已有类加点「私货」
扩展函数是 Kotlin 最让我上头的特性之一。你可以给一个类添加新方法,而不需要继承它。说白了,就是「偷懒」的合法途径。
举个例子,我经常需要把字符串转成整数,但用户输入可能不合法。Java 里你要写 try-catch,Kotlin 里直接这样:
val input: String = "123"
val number: Int? = input.toIntOrNull() // 如果解析失败,返回 null,不会抛异常
println(number) // 输出: 123
val badInput = "abc"
val badNumber = badInput.toIntOrNull()
println(badNumber) // 输出: null
我在项目中遇到过好几次因为 NumberFormatException 导致崩溃的情况。自从用了 toIntOrNull,这类 bug 几乎绝迹了。类似的还有 isNullOrBlank()、isNullOrEmpty(),都是日常高频使用的扩展。
String 加一个 isValidEmail() 方法,整个项目都能用。但别滥用,扩展函数适合「通用工具」,不适合业务逻辑。
作用域函数:let、run、with、apply、also
这五个函数是 Kotlin 的「五虎上将」。很多初学者容易搞混它们,其实记住两个维度就够了:上下文对象怎么访问(it 还是 this),以及返回值是什么(上下文对象本身还是 lambda 结果)。
我画了个表格,你一看就明白:
| 函数 | 上下文对象 | 返回值 | 典型场景 |
|---|---|---|---|
let |
it |
lambda 结果 | 非空判断 + 链式调用 |
run |
this |
lambda 结果 | 对象配置 + 计算结果 |
with |
this |
lambda 结果 | 对同一个对象做多次操作 |
apply |
this |
上下文对象本身 | 对象初始化配置 |
also |
it |
上下文对象本身 | 附加操作(如日志、校验) |
来,看几个代码例子,你就知道它们怎么用了。
let:我最常用的非空处理
val name: String? = "Kotlin"
name?.let {
// 这里 it 就是 name,且保证非空
println("名字长度: ${it.length}")
}
// 如果 name 是 null,let 里的代码不会执行
我曾经在项目里看到有人写 if (name != null) { ... },然后里面还要再解包。用 let 配合安全调用 ?.,代码简洁多了。
apply:配置对象的神器
val user = User().apply {
name = "张三"
age = 28
email = "zhangsan@example.com"
}
// apply 返回的是 User 对象本身,所以可以直接赋值给 user
你想想看,Java 里你要 new 一个对象,然后一行一行 set。Kotlin 里用 apply,所有配置写在一个块里,代码结构清晰,还省了临时变量。
also:顺手打个日志
val list = mutableListOf(1, 2, 3).also {
println("初始列表: $it")
}
list.add(4)
// also 返回列表本身,不影响后续操作
also 适合做「副作用」操作,比如日志、校验、缓存。它不会改变返回值,只是「顺便」做点事。
集合操作:告别 for 循环
Kotlin 的集合操作是我从 Java 转过来后最爽的体验之一。以前写 Java 要写 for 循环、if 判断、临时变量,现在一行链式调用搞定。
filter + map:最经典的组合
val numbers = listOf(1, 2, 3, 4, 5, 6)
val result = numbers
.filter { it % 2 == 0 } // 筛选偶数
.map { it * it } // 每个数平方
println(result) // 输出: [4, 16, 36]
你看,没有临时变量,没有 for 循环,代码读起来就像在描述「我要做什么」,而不是「我要怎么做」。这就是函数式编程的魅力。
groupBy:分组统计
data class Person(val name: String, val age: Int)
val people = listOf(
Person("张三", 25),
Person("李四", 30),
Person("王五", 25)
)
val grouped = people.groupBy { it.age }
println(grouped)
// 输出: {25=[Person(张三,25), Person(王五,25)], 30=[Person(李四,30)]}
我在项目中处理报表数据时,groupBy 几乎是必用的。按日期、按分类、按状态分组,一行代码搞定。
reduce 和 fold:聚合计算
val numbers = listOf(1, 2, 3, 4)
val sum = numbers.reduce { acc, i -> acc + i }
println(sum) // 输出: 10
// fold 可以指定初始值
val sumWithInit = numbers.fold(10) { acc, i -> acc + i }
println(sumWithInit) // 输出: 20
reduce 和 fold 的区别在于:reduce 用第一个元素作为初始值,fold 让你自己指定。我个人习惯用 fold,因为更明确,不容易出错。
sortedBy:排序
val names = listOf("Charlie", "Alice", "Bob")
val sorted = names.sortedBy { it.length }
println(sorted) // 输出: [Bob, Alice, Charlie]
排序也是高频操作。注意 sortedBy 返回新列表,原列表不变。如果你要原地排序,用 sortBy(只对可变列表有效)。
MutableList 上的 sort()、add() 等方法。
避坑指南:集合操作的性能陷阱
嗯,这里我要说一个我踩过的坑。链式调用虽然爽,但每个操作都会创建中间集合。比如:
val result = list
.filter { ... } // 创建第一个新列表
.map { ... } // 创建第二个新列表
.sortedBy { ... } // 创建第三个新列表
如果数据量很大(比如上万条),这种写法会创建多个临时集合,内存开销不小。我曾经在数据处理管道里这么写,结果 OOM 了。解决方案是用 Sequence(序列):
val result = list.asSequence()
.filter { ... }
.map { ... }
.sortedBy { ... }
.toList() // 只在最后一步创建集合
Sequence 是惰性求值的,每个元素经过整个管道后才处理下一个,不会创建中间集合。数据量大时,性能差距很明显。
总结
Kotlin 标准库的核心价值,就是让你用更少的代码,表达更清晰的意图。扩展函数让你给已有类添加能力,作用域函数帮你管理上下文和返回值,集合操作让你告别丑陋的 for 循环。
我个人建议:刚开始用的时候,先记住 let 和 apply 这两个最常用的,其他用到再查。别试图一次性记住所有函数,写多了自然就熟了。
最后送你一句话:好的代码不是写出来的,是「表达」出来的。Kotlin 标准库给了你表达的工具,剩下的就看你怎么用了。
公众号:蓝海资料掘金营,微信deep3321