1. 概述

with() 是 Kotlin 中的一个作用域函数(Scope Function),它的作用是在指定对象的上下文中执行一段代码块

本文将深入讲解 with() 函数的用法、与其他作用域函数的区别,以及实际使用中的注意事项。

2. with() 函数简介

with() 是 Kotlin 提供的五个作用域函数之一(其他包括 letrunapplyalso)。它的核心功能是:

在一个对象的上下文中执行一段代码块,并返回该代码块的执行结果。

下面是 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 }

⚠️ 因为没有显式返回值,所以 resultUnit 类型(相当于 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 仓库 获取。


原始标题:What Does “with” Mean in Kotlin?