Skip to content

作用域函数

源:Scope functions

Kotlin 标准库提供了 5 个著名的作用域函数:let, run, with, apply, also。它们本质上都是内联的带接收者的 高阶函数。

它们的共同目标是:在一个对象的上下文中执行代码块,从而使代码更加简洁、易读。

五大金刚:如何选择?

这 5 个函数非常相似,区别主要在于两点:

  1. 上下文对象如何引用:是 this 还是 it
  2. 返回值是什么:是对象本身还是Lambda 结果

决策矩阵

函数引用方式返回值典型用途
applythis对象本身对象配置、初始化
alsoit对象本身附加副作用(日志、打印),不打断链式调用
letitLambda 结果空检查、将对象作为参数传递、变换
runthisLambda 结果复杂计算、作用域隔离
withthisLambda 结果对同一个对象执行多个操作(非扩展函数)

详细实战

apply & also:返回对象本身

这两个函数适合处于调用链的中间,或者用于初始化。

  • apply (this): “我要配置这个对象。”

    kotlin
    val dialog = Dialog(context).apply {
        setTitle("Warn") // this.setTitle()
        setCancelable(false)
    }
  • also (it): “我要用这个对象做点额外的事(且不改变它)。”

    kotlin
    val book = createBook()
        .also { println("Created book: ${it.name}") } // 打印日志
        .also { repository.save(it) } // 保存

let & run:返回计算结果

这两个函数适合位于调用链的末尾,或者进行数据转换。

  • let (it): 最常用于空安全调用 ?.let

    kotlin
    val result = str?.let {
        println(it)
        it.length // 返回 Int
    }
  • run (this): 适合混合了对象配置与计算逻辑的场景。

    kotlin
    val width = view.run {
        measure() // this.measure()
        measuredWidth + padding // 返回计算结果
    }

with:独立的调用

with 不是扩展函数,它接收一个对象作为参数。读起来像“使用这个对象,做以下事情”。

kotlin
with(settings) {
    // 这里的 this 就是 settings
    load()
    applyChanges()
}

契约 (contracts) 的隐式支持

你在使用 runapply 初始化变量时,可能会发现编译器非常聪明:

kotlin
val service: Service
run {
    service = ServiceImpl()
}
println(service) // 编译器知道 service 已经被初始化了!

这得益于标准库在这些函数中使用了 Kotlin Contracts,明确告知了编译器:“这个 Lambda 块一定会执行,且只执行一次” 。虽然你不需要自己写契约,但了解这一点有助于你理解为什么 these 函数能无缝融入控制流分析。

条件执行:takeIf 与 takeUnless

这两个函数通常配合作用域函数使用,用于在链式调用中嵌入 if 逻辑。

  • takeIf { predicate }:如果满足条件返回 this,否则返回 null
  • takeUnless { predicate }:如果不满足条件返回 this,否则返回 null
kotlin
// 只有在文件可读且非空时才读取
val content = File("data.txt")
    .takeIf { it.exists() && it.canRead() }
    ?.readText()

反模式:不要滥用

虽然作用域函数很酷,但过度使用会降低可读性。

  1. 避免嵌套applyrunlet,会导致 thisit 指向混乱。如果必须嵌套,请显式命名 Lambda 参数。
  2. 避免链式过长:调试时很难在链式调用的中间打断点。
  3. let vs if:如果只是简单的空检查,有时 if (x != null)x?.let 更清晰。

总结

  • 配置对象 -> apply
  • 附加操作 -> also
  • 空检查/转换 -> let
  • 复杂逻辑/计算 -> run
  • 批量操作 -> with