Kotlin 作用域函数:apply/let/run/also 用错了一个,代码就变味了
作用域函数是 Kotlin 最常用的高级特性,用好能让代码简洁又清晰,用不好反而降低可读性。这篇文章帮你彻底分清这四个函数。
一句话核心区别
作用域函数就两个维度:
表格
| 维度 | 选项 |
|---|---|
| 内部用什么 | this(可省略)vs it |
| 返回什么 | 对象本身 vs 最后一行 |
1. apply:配置对象
特点:内部用 this,返回对象本身。
// 标准用法:new 完对象后,批量配置属性
val person = Person().apply {
name = "张三"
age = 25
address = "北京"
}
对比不用 apply:
val person = Person()
person.name = "张三"
person.age = 25
person.address = "北京"
Android 开发中的典型场景:
// 配置 View
val textView = TextView(context).apply {
text = "标题"
textSize = 16f
setTextColor(Color.RED)
gravity = Gravity.CENTER
}
// 配置 Intent
val intent = Intent(context, SecondActivity::class.java).apply {
putExtra("user_id", userId)
putExtra("name", name)
}
// 配置 RequestBody
val requestBody = FormBody.Builder().apply {
add("username", "张三")
add("password", "123456")
}.build()
什么时候用 apply:
- new 完对象,需要设置一堆属性
- 返回值必须是这个对象本身
2. let:判空处理
特点:内部用 it,返回最后一行。
// 标准用法:可空类型的安全处理
val name: String? = "张三"
val length = name?.let { it.length } ?: 0
对比不用 let:
val length = if (name != null) name.length else 0
链式调用中的判空:
// 对象链式调用,中间可能为 null
user?.getAddress()?.getCity()?.getName()
// 用 let 更清晰
val cityName = user?.getAddress()
?.let { it.getCity() }
?.let { it.getName() }
?: "未知城市"
经典场景:let + also(赋值前处理) :
// 创建一个列表,添加元素,然后使用
val list = mutableListOf<Int>().also {
it.add(1)
it.add(2)
it.add(3)
}
// list 现在是 [1, 2, 3]
什么时候用 let:
- 处理可空类型,避免嵌套 if-null
- 需要对非空值做转换
3. run:执行代码块并返回结果
特点:内部用 this,返回最后一行。
// 标准用法:基于对象执行一段逻辑,返回结果
val result = person.run {
println("处理 $name")
val adult = age >= 18
"成年状态:$adult" // 返回值
}
典型场景:takeIf 配合 run:
// 如果满足条件才处理
val discount = product
.takeIf { it.price > 100 }
?.run { price * 0.8 }
?: product.price
另一个经典用法:临时作用域:
// 临时作用域执行多个操作
val screenWidth = run {
val display = windowManager.defaultDisplay
val metrics = DisplayMetrics()
display.getMetrics(metrics)
metrics.widthPixels
}
什么时候用 run:
- 需要在对象上执行多个操作,返回最后结果
- 临时作用域执行代码
4. also:额外操作
特点:内部用 it,返回对象本身。
kotlin
// 标准用法:执行额外操作,返回原对象
val list = mutableListOf(1, 2, 3).also {
println("原始列表:$it")
// 可以在这里打日志、埋点等
}
对比 apply:
// apply:配置属性,返回自己
val person = Person().apply {
name = "张三" // 配置
}
// also:附加操作,返回自己
val list = mutableListOf(1, 2, 3).also {
println("创建列表") // 附加操作
}
什么时候用 also:
- 需要在链式调用中插入日志、埋点
- 需要执行不影响主逻辑的附加操作
5. 四大函数终极对照表
表格
| 函数 | 内部变量 | 返回值 | 最佳场景 |
|---|---|---|---|
apply | this | 对象本身 | 配置对象属性 |
let | it | 最后一行 | 判空、转换 |
run | this | 最后一行 | 执行逻辑、返回结果 |
also | it | 对象本身 | 附加操作、日志 |
6. 记忆口诀
plaintext
配置属性 → apply
判空转换 → let
执行返回 → run
附加日志 → also
7. 实战组合拳
场景:创建对象、配置、处理、返回
val response = api.getUser(userId)
.let { it.body() } // 提取 body
?.let {
UserDto(it.id, it.name, it.age)
} // 转换
?: UserDto(0, "默认用户", 0) // 空值兜底
val userView = UserView(response).also {
viewGroup.addView(it) // 添加到视图
log.d("创建 UserView: $response")
}
场景:Android 中的完整用法
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 1. 创建并配置 binding
binding = ActivityMainBinding.inflate(layoutInflater).also {
setContentView(it.root)
}
// 2. 配置 View
binding.tvTitle.apply {
text = "首页"
setTextColor(Color.BLACK)
}
// 3. 处理数据
viewModel.userData.observe(this) { user ->
user?.let { updateUI(it) } ?: showError()
}
}
private fun updateUI(user: User) {
binding.tvName.text = user.name
binding.tvAge.text = "${user.age}岁"
}
}
8. 常见问题
Q:apply 和 also 都能返回自己,区别在哪?
A:内部变量不同。apply 用 this(可省略),also 用 it。当你需要引用对象时用 also,只是配置属性时用 apply。
Q:let 和 run 都能返回最后一行,区别在哪?
A:内部变量不同。let 用 it,run 用 this。当你需要引用对象时用 let,主要做逻辑计算时用 run。
Q:什么时候不用作用域函数?
A:逻辑简单、一目了然的时候。比如:
// 简单赋值,不需要用 apply
val name = user.name
// 简单判空,不需要用 let
val length = name?.length ?: 0
总结
作用域函数是让代码更简洁的工具,不是必须用的银弹。选择原则:
- 配置对象属性 →
apply - 可空类型判空 →
let - 执行逻辑返回结果 →
run - 附加日志/操作 →
also