Kotlin 作用域函数笔记

Kotlin 作用域函数

作用域函数是 Kotlin 标准库中的一组函数,它们的唯一目的是在某个对象的上下文中执行一段代码块。共有 5 个:letapplyalsorunwith


终极对比速查表

函数上下文引用返回值是否扩展函数核心一句话记忆最适用场景
letit(可重命名)代码块最后一行让它去做转换,或安全判空。?.let 判空执行;将对象 A 转换为结果 B。
applythis(可省略)对象本身把这些属性应用到我身上。对象的初始化、UI 组件的集中配置。
alsoit(可重命名)对象本身原封不动交接,而且顺便做点事。链式调用中的副作用(打日志、埋点、校验)。
runthis(可省略)代码块最后一行在它内部跑一段逻辑并拿结果。复杂对象的构建与计算;初始化并立即产生结果。
withthis(可省略)代码块最后一行❌(普通函数)和它一起,集中调用它的方法。对同一个已存在的对象进行多次方法调用。
with 不是扩展函数,对象作为第一个参数传入,因此不适合用于可空对象(无法使用 ?.with)。

各函数详解

let —— 转换 & 判空

// 典型用法 1:安全判空(最常用)
currentUser?.let { user ->
    updateUI(user.name)
    sendAnalytics(user.id)
}

// 典型用法 2:将对象转换为另一种形式
val length = "Hello".let { str ->
    println("字符串是:$str")
    str.length  // 返回值
}
// length = 5

// 典型用法 3:限制变量作用域,避免污染外部
val result = someHeavyComputation().let { value ->
    value * 2 + 1
}

apply —— 初始化配置(返回自身)

// 典型用法 1:Android Paint 对象初始化
val paint = Paint().apply {
    color = Color.RED
    strokeWidth = 5f
    style = Paint.Style.STROKE
}

// 典型用法 2:Android View 配置
val button = Button(context).apply {
    text = "确认"
    textSize = 16f
    setOnClickListener { onConfirmClick() }
}

// 典型用法 3:构建请求参数
val config = SdkConfig().apply {
    environment = "PROD"
    timeout = 3000
    retryCount = 3
}

also —— 副作用(返回自身,不影响主流程)

// 典型用法 1:链式调用中插入日志
return repository.getUser(id)
    .also { Log.d("TAG", "Fetched user: ${it?.id}") }
    .also { analytics.track("user_fetched") }

// 典型用法 2:参数校验
fun processData(list: MutableList<String>) = list.also {
    require(it.isNotEmpty()) { "列表不能为空" }
}

// 典型用法 3:调试时临时打印中间值
val users = fetchUsers()
    .also { println("原始数据: $it") }
    .filter { it.isActive }
    .also { println("过滤后: $it") }

run —— 在对象上下文中执行并返回结果

// 典型用法 1:对象的复杂计算
val displayText = user.run {
    if (isVip) "$name VIP" else name
}

// 典型用法 2:可空对象判空 + 计算
val result = nullableObj?.run {
    doSomething()
    computeResult()  // 返回值
}

// 典型用法 3:作为独立的代码块(非扩展函数形式)
val value = run {
    val a = computeA()
    val b = computeB()
    a + b
}

with —— 对已有对象多次调用(非扩展函数)

// 典型用法 1:集中操作同一对象
with(binding) {
    tvTitle.text = "Hello"
    tvSubtitle.text = "World"
    btnAction.isVisible = true
}

// 典型用法 2:返回计算结果
val area = with(rectangle) {
    width * height
}

// 注意:不适合用于可空对象,改用 run
nullableObj?.run { doSomething() }  // 推荐替代 with

实战中的"肌肉记忆"

1. 遇到可空类型 ?,首选 let

currentUser?.let { user ->
    updateUI(user.name)
}

2. new 一个对象并需要设置属性,无脑选 apply

val config = SdkConfig().apply {
    environment = "PROD"
    timeout = 3000
}

3. 函数返回前想插一脚(比如打日志),用 also

return database.getUser().also {
    Log.d("SDK", "Fetched user: ${it.id}")
}

4. 需要在对象上下文中计算出一个新值,用 run

val displayString = jsonParser.run {
    val parsed = parse(rawJson)
    format(parsed)
}

5. 对已有对象集中调用多个方法,用 with

with(textView) {
    text = "Kotlin"
    textSize = 18f
    setTextColor(Color.BLACK)
}

选择决策树

需要用作用域函数?
│
├─ 对象可能为 null?
│   ├─ 是 → 需要返回新结果?
│   │        ├─ 是 → let
│   │        └─ 否 → let 或 run
│   └─ 否 ↓
│
├─ 是否要返回对象本身(链式调用)?
│   ├─ 是 → 需要 it 引用?
│   │        ├─ 是 → also(副作用)
│   │        └─ 否 → apply(初始化配置)
│   └─ 否 ↓
│
├─ 对象是否已存在(非新建)?
│   ├─ 是,多次调用其方法 → with
│   └─ 否,需要计算新值 → run

终极防混淆口诀

  • 改变/配置自己 → apply
  • 转换出新结果 → let / run
  • 顺便看一眼(不改变业务流) → also
  • 集中调用已有对象的方法 → with

常见反模式(别这样写)

// 嵌套多层作用域函数,可读性极差
obj.let { it.apply { config() }.also { log(it) } }  // 不推荐

// 推荐:拆开写,清晰
obj.apply { config() }
   .also { log(it) }

// apply 做转换(apply 返回自身,拿不到转换结果)
val name = user.apply { "Hello, $name" }  // 返回的是 user,不是字符串!

// 推荐:用 let 做转换
val greeting = user.let { "Hello, ${it.name}" }

// 对非空对象用 let 做简单调用,没必要
val nonNull = "hello"
nonNull.let { it.uppercase() }  // 多此一举
"hello".uppercase()  // 直接调用即可

本文链接:

https://www.devorz.com/index.php/archives/kotlin-scope-functions.html
1 + 6 =
快来做第一个评论的人吧~