1. 概述
with()
是 Kotlin 中的一个作用域函数(Scope Function),它的作用是在指定对象的上下文中执行一段代码块。
本文将深入讲解 with()
函数的用法、与其他作用域函数的区别,以及实际使用中的注意事项。
2. with()
函数简介
with()
是 Kotlin 提供的五个作用域函数之一(其他包括 let
、run
、apply
、also
)。它的核心功能是:
在一个对象的上下文中执行一段代码块,并返回该代码块的执行结果。
下面是 with()
的函数定义:
public inline fun <T, R> with(receiver: T, block: T.() -> R): R
参数说明如下:
receiver
:上下文对象,即我们要在其上下文中执行代码的对象block
:一个以T.()
为接收者的 lambda 表达式,代表要执行的逻辑- 返回值:lambda 表达式的执行结果
接下来我们通过一个例子来说明它的使用。
3. with()
的使用示例
假设我们有以下数据类 Player
:
data class Player(val firstname: String, val lastname: String, val totalPlayed: Int, val numOfWin: Int)
我们希望为 Player
实例生成一个描述字符串,格式为:
"firstname lastname's win-rate is rate%"
使用 with()
实现如下:
val tomHanks = Player(firstname = "Tom", lastname = "Hanks", totalPlayed = 100, numOfWin = 77)
val expectedDescription = "Tom Hanks's win-rate is 77%"
val result = with(tomHanks) {
"$firstname $lastname's win-rate is ${numOfWin * 100 / totalPlayed}%"
}
assertEquals(expectedDescription, result)
✅ 上面代码中,tomHanks
是上下文对象,with()
内部可以直接访问其属性。最终返回的是字符串结果。
⚠️ 注意:with()
返回的是 lambda 的执行结果,而不是上下文对象本身。
如果我们想在 block 中引用上下文对象本身,可以使用 this
:
val result = with(tomHanks) {
"$this"
}
assertEquals("$tomHanks", result)
✅ 在字符串模板中,$this
等价于 this.toString()
。
4. with()
与 apply()
的区别
with()
和 apply()
都使用 this
来引用上下文对象,但它们的返回值不同:
函数 | 返回值 | 典型用途 |
---|---|---|
with() |
lambda 执行结果 | 执行逻辑并返回某个值 |
apply() |
上下文对象本身 | 对象初始化或配置 |
举个例子:
class Player2(val id: Int) {
var firstname: String = ""
var lastname: String = ""
var totalPlayed: Int = 0
var numOfWin: Int = 0
}
使用 apply()
进行对象初始化:
val tomHanks = Player2(7).apply {
firstname = "Tom"
lastname = "Hanks"
totalPlayed = 100
numOfWin = 77
}
而使用 with()
则会返回 lambda 的结果:
val result = with(Player2(7)) {
firstname = "Tom"
lastname = "Hanks"
totalPlayed = 100
numOfWin = 77
}
assertTrue { result is Unit }
⚠️ 因为没有显式返回值,所以 result
是 Unit
类型(相当于 Java 的 void
)。
另外,调用方式也不同:
someObject.apply { ... }
with(someObject) { ... }
这是因为 apply()
是一个扩展函数,而 with()
不是。
5. with()
与 run()
的区别
run()
和 with()
的功能非常相似,都返回 lambda 的执行结果。它们的区别在于调用方式:
函数 | 调用方式 | 是否是扩展函数 |
---|---|---|
with() |
with(obj) { ... } |
❌ 不是 |
run() |
obj.run { ... } |
✅ 是 |
举个例子:
val result1 = with(obj) { ... }
val result2 = obj.run { ... }
两者返回的结果类型相同,但在处理可空对象时,run()
更加直观和安全。
比如我们有以下函数:
fun giveMeAPlayer(): Player? {
return Player("Tom", "Hanks", 100, 77)
}
使用 run()
处理可空对象:
val runResult = giveMeAPlayer()?.run {
"$firstname $lastname's win-rate is ${numOfWin * 100 / totalPlayed}%"
}
assertEquals("Tom Hanks's win-rate is 77%", runResult)
而使用 with()
时,必须手动判断是否为 null:
val withResult = with(giveMeAPlayer()) {
if (this != null) {
"$firstname $lastname's win-rate is ${numOfWin * 100 / totalPlayed}%"
} else {
null
}
}
assertEquals("Tom Hanks's win-rate is 77%", withResult)
✅ 总结:如果上下文对象可能为 null,建议优先使用 run()
,这样可以利用 Kotlin 的 null 安全操作符 ?.run
,代码更简洁清晰。
6. 总结
函数 | 引用方式 | 返回值 | 典型使用场景 |
---|---|---|---|
with() |
this |
lambda 执行结果 | 在对象上下文中执行逻辑并返回结果 |
apply() |
this |
上下文对象本身 | 对象初始化或配置 |
run() |
this |
lambda 执行结果 | 与 with() 类似,但更适合处理可空对象 |
✅ 推荐使用原则:
- 如果你需要返回一个值,使用
with()
或run()
- 如果你需要返回对象本身(用于链式调用),使用
apply()
- 如果上下文对象可能为 null,优先使用
run()
最后,完整代码示例可在 GitHub 仓库 获取。