applay, also,let, run, with 是kotlin标准库提供的5个主要的作用域函数(Scope Functions),它们的设计目的是为了在特定作用域内更简洁地操作对象。
如何使用这5个函数,要从它的设计目的来区分:
- apply : 配置/对象初始化, 返回对象本身
- also : 副作用操作/链式中间处理,返回对象本身
- let : 空安全转换/数据映射,返回Lambda结果
- run : 对象计算/链式操作,返回Lambda结果
- with : 非扩展函数版的
run
,返回Lambda结果
深度解析每个函数
1.apply
public inline fun <T> T.apply(block: T.() -> Unit): T {block() // 配置对象return this // 返回自身
}
特点:
- 隐式
this
引用 - 专为对象初始化/配置设计
- Lambda 是扩展函数:可以直接访问对象成员
val dialog = AlertDialog.Builder(context).apply {setTitle("提示")setCancelable(false)}
这是一个非常典型的建造者模式。其内部的setTitle方法可能是这样的:
class Builder (private val context: Context) {private var title: String = ""fun setTitle(title: String) = apply {this.title = title}
}
上面的代码你可能看着有些奇怪 = apply {}
上面的代码等效于:
fun setTitle(title: String): Builder {this.title = titlereturn this
}
这里会引入一个概念单表达式函数(Single-Expression Functions)
fun add(a: Int, b: Int): Int {return a + b
}// 标准形式:用 = 替代 { return ... }
fun add(a: Int, b: Int): Int = a + b
在看上面 无论 block
里有多少行代码,apply{}
本身是一个返回 this
的表达式, 如果你还不理解
fun max(a: Int, b: Int){if (a > b)return aelse return b
}fun max(a: Int, b: Int): Int = if (a > b) a else b
它只是需要一个表达式,而apply{}恰好满足这个表达式
无论 block
里有多少行代码,apply
本身是一个返回 this
的表达式
val person = Person().apply {name = "John" // this.name = "John"age = 30 // this.age = 30city = "New York" // this.city = "New York"
}
这个是一个初始化的例子等价与
val person = Person()
person.name = "John"
person.age = 30
person.city = "New York"
2.also
public inline fun <T> T.also(block: (T) -> Unit): T {block(this) // 执行副作用return this // 返回自身
}
特点:
- 显式
it
引用, 让副作用操作更明确 - 可空对象处理
- 适合调试日志或链式调用中的中间操作
- Lambda 是普通函数:必须通过
it
引用对象
phone?.also {require(it)//副作用
}?.process()
验证并继续使用原对象。判空如果不做类型转换建议使用also。
3.let
public inline fun <T, R> T.let(block: (T) -> R): R {return block(this) // 将 this 作为参数传入 lambda
}
特点:
- 用
it
引用对象 - 适合可空对象处理和类型转换
val length = phone?.let { it.length } ?: 0
它的行为是把一个String类型转换成了一个Int类型,类型转换是它的重点。
4.run
public inline fun <T, R> T.run(block: T.() -> R): R {return block() // 以扩展函数方式调用
}
特点:
- 用
this
引用对象 - 可与
?.
结合处理可空对象 - 适合同时访问对象属性和返回计算结果
val description = user.run { "$name: ${calculateScore()}" }
5.with
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {return receiver.block() // 非扩展函数版本
}
特点:
- 非扩展函数,需要显式传入接收者对象
- 不能直接结合
?.
处理可空对象(需要额外判空) - 适合集中操作一个对象的场景
val result = with(config) {validate()buildResult()
}
我们可以看到这5个都是内联函数(inline functions),其核心机制就是在编译时进行代码拷贝(或称"代码展开"),而不是在运行时进行函数调用。
为什么 Kotlin 标准库函数用 inline?
- 避免 lambda 对象创建:如果不内联,每次调用都会生成一个匿名类实例
- 支持
return
控制流:内联后 lambda 中的return
可以直接从外层函数返回