以下是 Kotlin 中作用域函数 let、with、run、also、apply 的核心使用指南,结合其特性、适用场景及代码示例整理而成:
🧩 一、作用域函数核心对比
| 函数 | 上下文对象引用 | 返回值 | 典型场景 |
|---|---|---|---|
**let** | it | Lambda 结果 | 空安全操作、数据转换、链式处理中间结果 |
**run** | this | Lambda 结果 | 对象配置与计算混合、替代 with(扩展函数形式)、链式空安全处理 |
**with** | this | Lambda 结果 | 集中操作非空对象的多属性/方法(需显式传入对象) |
**apply** | this | 对象本身 | 对象初始化配置(类似 Builder 模式)、多属性设置 |
**also** | it | 对象本身 | 附加操作(如日志、验证)、链式调用中插入副作用 |
💡 选择关键:
- 需返回对象本身 →
apply/also;需返回计算结果 →let/run/with。- 上下文引用偏好 →
this(run/with/apply)更简洁;it(let/also)避免命名冲突。- 空安全 →
?.let或?.run优先。
⚙️ 二、分函数详解与示例
1. **let:空安全转换与作用域隔离**
-
场景:处理可空对象、数据转换、链式操作中间处理。
-
示例:
val name: String? = "Kotlin" val length = name?.let { println("Processing: $it") // 输出:Processing: Kotlin it.length // 返回结果 } ?: 0 // 提供默认值典型链式转换:
val numbers = listOf(1, 2, 3) val doubled = numbers .filter { it > 1 } .let { it.map { num -> num * 2 } } // [4, 6]
2. **run:对象配置与计算混合**
-
场景:对象初始化+结果计算、替代
with(扩展函数形式)、临时作用域创建。 -
示例:
// 扩展函数形式(配置对象并返回结果) val personInfo = Person("Alice", 25).run { name = "Bob" // 直接修改属性(this 可省略) age += 1 "Name: $name, Age: $age" // 返回字符串 } // 输出:Name: Bob, Age: 26 // 非扩展形式(替代临时变量) val fullName = run { val firstName = "John" val lastName = "Doe" "$firstName $lastName" // 返回拼接结果 }
3. **with:集中操作非空对象**
-
场景:对同一对象执行多步操作(无需重复写对象名)。
-
示例:
val person = Person("Bob", 25) val info = with(person) { age = 26 // 直接访问属性 "Name: $name, Age: $age" // 返回结果 }
4. **apply:对象初始化配置**
-
场景:构建对象时批量设置属性(返回自身,支持链式)。
-
示例:
val person = Person().apply { name = "Charlie" age = 28 address = "Paris" } // 返回已配置的 Person 对象
5. **also:链式调用附加操作**
-
场景:日志记录、数据验证等副作用操作(不影响对象本身)。
-
示例:
mutableListOf(1, 2, 3) .also { println("初始列表: $it") } // 输出:初始列表: [1, 2, 3] .add(4)
🛠️ 三、进阶技巧与避坑指南
-
链式组合:
-
apply+also:初始化后记录日志val file = File("data.txt").apply { createNewFile() }.also { println("创建文件: ${it.path}") } -
let+run:空安全转换后继续操作user?.let { it.validate() }?.run { process(this) }
-
-
空安全优先策略:
- 可空对象操作 →
?.let {} ?: default - 避免
apply/also在可空对象上直接调用(需手动判空)。
- 可空对象操作 →
-
性能优化:
- 所有作用域函数均为
inline,无运行时开销(编译期内联优化)。 在 Kotlin 中,作用域函数(let、run、with、apply、also)的选择流程可基于 返回值需求、空安全处理、上下文引用方式(this/it)和链式调用需求综合判断。以下是具体选择流程及示例说明:
- 所有作用域函数均为
🔍 选择流程与示例
⚙️ 步骤1:是否需要返回对象本身?
- 是 → 进入步骤2
- 否 → 进入步骤3
示例场景:初始化对象或执行副作用操作后需继续操作原对象。
// 初始化对象并返回自身 → 选 apply
val button = Button().apply {
text = "Submit"
color = Color.BLUE
} // 返回 Button 对象
// 执行日志后返回原对象 → 选 also
val list = mutableListOf(1, 2).also {
println("Initial list: $it") // 输出日志
}.add(3) // 继续操作原对象
⚙️ 步骤2:是否需要隐式接收者(this)?
- 是 → **
apply**(对象配置) - 否 → **
also**(附加操作,用it引用)
示例对比:
// apply:用 this 配置属性(简洁)
val config = Config().apply {
host = "localhost" // 等价于 this.host
port = 8080
}
// also:用 it 执行日志(避免命名冲突)
val user = User("Alice").also {
println("Created user: ${it.name}") // it 显式引用
}
⚙️ 步骤3:是否需要处理可空对象?
- 是 → **
let或run**(支持?.安全调用) - 否 → 进入步骤4
示例场景:
// let:处理可空字符串
val name: String? = "Kotlin"
val length = name?.let {
println("Processing: $it")
it.length // 返回长度
} ?: 0 // 提供默认值
// run:可空对象的多步操作
val user: User? = getUser()
user?.run {
// this 引用对象
updateProfile(this)
getProfileInfo() // 返回结果
}
⚙️ 步骤4:是否需要链式调用或扩展函数形式?
- 是 → **
run**(扩展函数,支持链式) - 否 → **
with**(显式传入对象)
示例对比:
// run:链式转换数据
val result = dataSource.fetchData()
.run {
filter { it.isValid } // 操作数据
convertToModel()
} // 返回模型
// with:集中操作非空对象
val list = mutableListOf("A", "B")
val size = with(list) {
add("C")
remove("A")
size // 返回结果
}
⚠️ 注意事项
-
空安全优先:
- 可空对象操作避免直接使用
apply/also(需手动判空),优先选?.let或?.run。 - 错误示例:
nullableObj.apply { ... }(可能抛 NPE)。
- 可空对象操作避免直接使用
-
组合使用:
-
apply+also:初始化后记录日志val file = File("data.txt").apply { createNewFile() }.also { println("Created: ${it.path}") } -
let+run:空安全转换后继续操作user?.let { it.validate() }?.run { process(this) }
-
-
性能优化:
所有作用域函数均为inline,无运行时开销。
通过以上流程和示例,可快速定位适合当前场景的作用域函数,兼顾代码简洁性、安全性与可读性。实际开发中需结合具体需求灵活选择,避免生搬硬套。
💎 四、总结选择流程图
flowchart TD
A["需返回对象本身?"]
A -->|是| B{"需用 this 配置?"}
B -->|是| C["apply"]
B -->|否| D["also"]
A -->|否| E{"需空安全?"}
E -->|是| F{"需链式?"}
F -->|是| G["run"]
F -->|否| H["let"]
E -->|否| I{"需扩展函数?"}
I -->|是| J["run"]
I -->|否| K["with"]
通过明确操作目标(配置、转换、副作用)和上下文需求(空安全、链式、作用域隔离),可精准选用最合适的函数。
整体感觉还是很复杂,记不住!!!
基于这篇详尽的 Kotlin 作用域函数指南,以下是 极简记忆口诀 和 核心区分逻辑,帮你快速掌握精髓:
🧠 一分钟记忆口诀
"配自己用 apply,加操作选 also"
"空安全找 let,计算混用 run"
"with 开会效率高,对象在握不奔波"
🔑 核心区分逻辑(按优先级判断)
-
要不要返回对象本身?
- ✅ 是 → 进入 2
- ❌ 否 → 进入 3
-
用
this还是it?- 直接调属性 →
apply(如name = "Kotlin") - 需对象引用 →
also(如println(it))
- 直接调属性 →
-
对象可能为空吗?
- ✅ 是 → 用
?.let或?.run - ❌ 否 → 进入 4
- ✅ 是 → 用
-
需要链式调用吗?
- ✅ 是 →
run(扩展函数形式) - ❌ 否 →
with(集中操作非空对象)
- ✅ 是 →
⚡️ 高频场景速查表
| 场景 | 推荐函数 | 示例片段 |
|---|---|---|
| 初始化对象 | apply | TextView().apply { text = "Hi" } |
| 链式调用中打印日志 | also | list.also { log(it) }.add(item) |
| 可空对象转换 | ?.let | name?.let { it.uppercase() } |
| 对象计算+属性修改 | run | val result = obj.run { update(); getValue() } |
| 集中操作非空对象 | with | with(list) { add("A"); size } |
💡 避坑提醒
- 空安全第一:可空对象别直接用
apply/also(会 NPE),加?.或改用let/run - 拒绝嵌套地狱:深层嵌套时换行写,保持可读性
user?.let { u ->
u.validate().run {
process(this)
}
}
口诀 + 3 步判断法,实践中形成肌肉记忆后基本 5 秒内可选出正确函数。建议保存速查表,遇到犹豫时快速对照!