Kotlin 作用域函数:apply, let, run, with, also 详解
Kotlin 作用域函数:apply, let, run, with, also 详解
在 Kotlin 中,作用域函数(Scope Functions)是一组用于简化代码、控制作用域的特殊函数。它们能让你在特定对象的上下文中执行代码块,减少重复代码并提高可读性。本文将详细介绍最常用的五个作用域函数:apply、let、run、with 和 also。
核心区别
所有作用域函数的核心区别体现在两个方面:
- 上下文对象的引用方式:使用
this还是it - 返回值:返回上下文对象本身还是 lambda 表达式的结果
| 函数 | 上下文引用 | 返回值 | 扩展函数 | 典型用途 |
|---|---|---|---|---|
| apply | this(可省略) | 上下文对象本身 | 是 | 对象初始化、配置 |
| let | it(可自定义) | lambda 最后一行结果 | 是 | 非空处理、临时变量作用域 |
| run | this(可省略) | lambda 最后一行结果 | 是 | 链式调用、访问成员并返回结果 |
| with | this(可省略) | lambda 最后一行结果 | 否 | 集中操作同一对象 |
| also | it(可自定义) | 上下文对象本身 | 是 | 附加操作、日志记录 |
详细解析
1. apply
- 上下文引用:
this(代表调用对象,可省略) - 返回值:上下文对象本身
- 特点:作为扩展函数,专注于对对象进行配置,最终返回该对象
val person = Person().apply {
name = "Alice" // 等价于 this.name = "Alice"
age = 30 // 直接访问对象成员,省略 this
city = "New York"
}
// apply 返回配置后的 person 对象
典型场景:
- 对象初始化和配置
- 构建者模式替代
- 需要对对象进行一系列设置后继续使用该对象
2. let
- 上下文引用:
it(默认参数名,可自定义) - 返回值:lambda 表达式最后一行的结果
- 特点:作为扩展函数,适合处理非空对象和限定变量作用域
val str: String? = "Hello Kotlin"
val result = str?.let {
// 只有当 str 非空时才执行
println("长度:${it.length}") // it 代表 str
it.uppercase() // 返回转换后的字符串
}
// result 为 "HELLO KOTLIN"
自定义参数名:
str?.let { s ->
println(s.length) // 使用自定义参数名 s 代替 it
}
典型场景:
- 非空安全处理(配合
?.操作符) - 限定临时变量的作用域
- 链式调用中转换数据
3. run
- 上下文引用:
this(代表调用对象,可省略) - 返回值:lambda 表达式最后一行的结果
- 特点:作为扩展函数,结合了
let和with的优点,支持链式调用
val numbers = listOf(1, 2, 3, 4)
val result = numbers.run {
println("原始列表:$this") // this 代表 numbers 列表
filter { it % 2 == 0 } // 筛选偶数
.map { it * 2 } // 每个元素乘以 2
}
// result 为 [4, 8]
典型场景:
- 需要访问对象成员并返回结果的场景
- 链式调用处理(特别是需要中间转换的情况)
- 替代
with进行链式操作
4. with
- 上下文引用:
this(代表传入的对象,可省略) - 返回值:lambda 表达式最后一行的结果
- 特点:不是扩展函数,需要显式传入上下文对象
val book = Book("Kotlin Guide", "John Doe")
val description = with(book) {
// 集中操作 book 对象
"Title: $title, Author: $author, Pages: $pages" // 直接访问成员
}
// description 为书籍描述字符串
典型场景:
- 集中操作同一个对象的多个成员
- 避免重复书写对象名
- 不需要链式调用的场景
5. also
- 上下文引用:
it(默认参数名,可自定义) - 返回值:上下文对象本身
- 特点:作为扩展函数,适合执行附加操作,不影响原对象
val numbers = mutableListOf(1, 2, 3)
numbers.also {
println("原始列表:$it") // 记录日志(附加操作)
}.addAll(listOf(4, 5, 6)) // 继续操作原对象
// also 返回原 numbers 对象,已添加新元素
典型场景:
- 执行附加操作(如日志记录、调试信息)
- 需要使用对象但不修改它时
- 链式调用中添加副作用操作
选择指南
-
当需要配置对象并返回该对象时,使用
applyval dialog = AlertDialog.Builder(context).apply { setTitle("Title") setMessage("Message") setPositiveButton("OK", null) }.create() -
当需要处理非空对象或转换数据时,使用
letuser?.let { safeUser -> // 确保 user 非空 saveUser(safeUser) navigateToProfile(safeUser.id) } -
当需要访问对象成员并返回结果,或进行链式操作时,使用
runval total = order.run { calculateSubtotal() applyDiscount() addTax() } -
当需要集中操作同一对象且不需要链式调用时,使用
withwith(canvas) { drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint) drawText("Hello", x, y, textPaint) setLayerType(LAYER_TYPE_HARDWARE, null) } -
当需要执行附加操作(如日志)且不改变返回对象时,使用
alsoval data = fetchData() .also { log("Fetched data: ${it.size} items") } .filter { it.isValid } .also { log("Filtered data: ${it.size} items") }
总结
作用域函数的主要价值在于:
- 减少重复代码(避免多次写对象名)
- 明确代码作用域
- 提高代码可读性和流畅性
选择合适的作用域函数可以让代码更简洁、更具表达力。记住它们的核心区别(上下文引用和返回值),根据具体场景选择最合适的函数即可。

浙公网安备 33010602011771号