Kotlin-作用域函数

本文介绍了Kotlin中的一些作用域函数,如let,run,with,apply和also,它们用于在对象上下文中执行代码块。let常用于处理非空值,with和run在不需返回结果时操作对象,apply用于对象配置,also则用于在操作对象的同时保持对它的引用。这些函数能提高代码的简洁性和可读性,但也需要注意避免过度使用和混淆上下文。

在对象的上下文中执行代码块。当您在提供lambda表达式的对象上调用这样的函数时,它会形成一个临时作用域。在此范围内,可以不带名称地访问对象。这样的函数称为作用域函数。
let run with apply also
作用域函数不会引入任何新的技术功能,但它们可以使代码更加简洁易读。
尽管作用域函数可以使代码更加简洁,但要避免过度使用它们:这会使代码难以阅读并导致错误。我们还建议避免嵌套作用域函数,并在链接它们时要小心,因为很容易混淆当前上下文对象和this或it的值。

对比
函数对象引用返回值是否是扩展函数
letitLambda resultYes
runthisLambda resultYes
run-Lambda resultNo:在没有上下文对象的情况下调用
withthisLambda resultNo:以上下文对象作为参数。是顶层函数
applythisContext object(不会返回闭包结果)Yes
alsoitContext object(不会返回闭包结果)Yes
let

可用于对调用链的结果调用一个或多个函数。例如,下面的代码打印一个集合上的两个操作的结果:

val numbers = mutableListOf("one", "two", "three", "four", "five")
val resultList = numbers.map { it.length }.filter { it > 3 }
println(resultList) 

val numbers = mutableListOf("one", "two", "three", "four", "five")
numbers.map { it.length }.filter { it > 3 }.let { 
    println(it)
    // and more function calls if needed
} 

Let通常用于执行包含非空值的代码块。要对非空对象执行操作,请使用安全调用运算符?。

val str: String? = "Hello"   
//processNonNullString(str)       // compilation error: str can be null
val length = str?.let { 
    println("let() called on $it")        
    processNonNullString(it)      // OK: 'it' is not null inside '?.let { }'
    it.length
}

Fragment 中获取参数

arguments?.let {
   param1 = it.getString(ARG_PARAM1)
}
with

不能判空
建议在不需要使用返回结果时使用with来调用上下文对象上的函数。在代码中,with可读为“使用此对象,执行以下操作。”

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    println("'with' is called with argument $this")
    println("It contains $size elements")
}
run

Run的功能与with相同,但它是作为扩展函数实现的。
当你的lambda既初始化对象又计算返回值时,Run很有用。

val service = MultiportService("https://example.kotlinlang.org", 80)

val result = service.run {
    port = 8080
    query(prepareRequest() + " to port $port")
}

// the same code written with let() function:
val letResult = service.let {
    it.port = 8080
    it.query(it.prepareRequest() + " to port ${it.port}")
}

还可以将run作为非扩展函数调用。run的非扩展变体没有上下文对象,但它仍然返回lambda结果。非扩展运行允许您在需要表达式的地方执行由几个语句组成的块。

val hexNumberRegex = run {
    val digits = "0-9"
    val hexDigits = "A-Fa-f"
    val sign = "+-"

    Regex("[$sign]?[$digits$hexDigits]+")
}

for (match in hexNumberRegex.findAll("+123 -FFFF !%*& 88 XYZ")) {
    println(match.value)
}
apply

建议您将它用于不返回值且主要操作receiver对象成员的代码块。apply最常见的用例是用于对象配置。这样的调用可以理解为“对对象应用下列赋值”。

val adam = Person("Adam").apply {
    age = 32
    city = "London"        
}
println(adam)

apply的另一个用例是在多个调用链中包含apply,以进行更复杂的处理。

Fragment 的参数赋值

arguments = Bundle().apply {
      putString(ARG_PARAM1, param1)
}
also

对于需要引用对象而不是其属性和函数的操作,或者当您不想从外部作用域遮挡this引用时,使用also。

当您在代码中看到also时,您可以将其读为“并且还对对象执行以下操作”。

val numbers = mutableListOf("one", "two", "three")
numbers
    .also { println("The list elements before adding new one: $it") }
    .add("four")
资料

https://kotlinlang.org/docs/scope-functions.html

<think>我们正在讨论Kotlin作用域函数:let、run、with、apply、also。它们都是用来在对象的上下文中执行代码块,但各有特点。根据引用内容,我们可以从以下几个方面区分它们: 1. 上下文对象的引用方式(是`this`还是`it`) 2. 返回值(返回代码块的最后一行还是对象本身) 3. 适用场景 下面我们逐一分析: 1. **let** - 上下文对象引用:作为lambda参数(`it`) - 返回值:代码块的最后一行 - 适用场景: - 处理可空对象(配合`?.`安全调用) - 对对象进行转换操作 - 需要明确使用对象作为参数时 - 示例(来自引用[3]): ```kotlin val nameLength = person?.let { it.name.length // 返回长度 } ``` 2. **run** - 有两种形式: - 扩展函数形式:上下文对象是`this`(可省略) - 非扩展函数形式:直接在代码块中执行,无上下文对象 - 返回值:代码块的最后一行 - 适用场景: - 需要执行一个代码块并返回结果 - 对象初始化并计算一些值 - 链式调用中需要执行多个操作 - 示例: ```kotlin val result = person.run { // 这里this指向person println(name) // 直接访问属性 age * 2 // 返回最后一行 } ``` 3. **apply** - 上下文对象引用:`this`(可省略) - 返回值:对象本身 - 适用场景: - 对象配置(设置多个属性) - 初始化对象 - 示例: ```kotlin val person = Person().apply { name = "Alice" age = 30 // 返回Person对象本身 } ``` 4. **also** - 上下文对象引用:作为lambda参数(`it`) - 返回值:对象本身 - 适用场景: - 需要对象作为参数执行额外操作(如打印日志)而不改变对象 - 链式调用中插入附加操作 - 示例: ```kotlin val person = Person().also { println("Created person: ${it.name}") // 打印信息 // 返回Person对象本身 } ``` 5. **with** - 非扩展函数:将对象作为参数传入 - 上下文对象引用:`this`(可省略) - 返回值:代码块的最后一行 - 适用场景: - 对同一个对象执行多个操作(不需要链式调用) - 将对象作为参数进行一系列操作 - 示例: ```kotlin val result = with(person) { println(name) age * 2 // 返回最后一行 } ``` **总结表格**: | 函数 | 上下文对象 | 返回值 | 主要适用场景 | |--------|------------|--------------|-------------------------------------------| | `let` | `it` | 代码块最后一行 | 安全调用、转换操作、参数传递 | | `run` | `this` | 代码块最后一行 | 对象计算、链式调用、无上下文执行代码块 | | `with` | `this` | 代码块最后一行 | 对已有对象执行多个操作(非扩展) | | `apply`| `this` | 对象本身 | 对象初始化、配置属性 | | `also` | `it` | 对象本身 | 附加操作(如日志)、链式调用中不改变对象 | **选择指南**(参考引用[2]): - 需要返回对象本身? - 是:选择`apply`(配置对象)或`also`(附加操作) - 否:进入下一步 - 使用`this`作为上下文? - 是:选择`run`(需要返回值)或`with`(已有对象且不需要扩展) - 否:选择`let`(需要`it`作为参数) **示例场景对比**: 1. **安全调用(可空对象)**:用`let` ```kotlin person?.let { sendEmail(it) } ``` 2. **对象配置**:用`apply` ```kotlin val dialog = AlertDialog.Builder(context).apply { setTitle("Title") setMessage("Message") }.create() ``` 3. **计算属性**:用`run` ```kotlin val ageAfterTenYears = person.run { age + 10 } ``` 4. **附加操作**:用`also` ```kotlin val book = Book().also { println("Book created: $it") } ``` 5. **对已有对象操作**:用`with` ```kotlin with(recyclerView) { layoutManager = LinearLayoutManager(context) adapter = MyAdapter() } ``` 通过以上对比,我们可以根据具体需求选择合适的作用域函数,使代码更加简洁清晰。 §§相关问题§§ 1.Kotlin中如何处理嵌套作用域函数的上下文冲突? 2. 为什么`run`函数有两种形式?它们分别适用于什么情况? 3. 如何利用作用域函数优化Kotlin代码的可读性?</think>### Kotlin 作用域函数详解 Kotlin作用域函数(`let`、`run`、`apply`、`also`、`with`)都是在对象上下文中执行代码块的工具,但它们在**上下文对象引用方式**和**返回值**上存在关键差异: | 函数 | 上下文对象 | 返回值 | 典型使用场景 | |---------|------------|--------------|---------------------------------------| | `let` | `it` | 代码块最后一行 | 空安全检查、对象转换、链式调用[^3] | | `run` | `this` | 代码块最后一行 | 对象初始化+计算、链式操作[^1] | | `apply` | `this` | 对象本身 | 对象配置(多属性设置)[^1] | | `also` | `it` | 对象本身 | 附加操作(日志/验证)、链式调用[^2] | | `with` | `this` | 代码块最后一行 | 非空对象的多操作(替代Java点语法)[^1] | --- ### 核心区别详解 #### 1. **`let` vs `run`** ```kotlin // let:使用 it 访问对象,返回最后一行 val length = user?.let { println("Processing ${it.name}") it.name.length // 返回值 } // run:使用 this 访问对象,返回最后一行 val bio = user.run { "$name, $age years old" // 直接访问属性 } ``` - ✅ 选 `let`:需要空安全检查或对象作为参数传递 - ✅ 选 `run`:对象已非空且需直接访问属性[^1] #### 2. **`apply` vs `also`** ```kotlin // apply:配置对象属性,返回对象本身 val button = Button().apply { text = "Submit" isEnabled = false } // 返回配置好的Button // also:附加操作,返回对象本身 val file = File("data.txt").also { require(it.exists()) // 验证文件存在 println("Created: $it") } // 返回原File对象 ``` - ✅ 选 `apply`:初始化对象并设置多个属性 - ✅ 选 `also`:执行与对象相关的附加操作(日志/验证)[^2] #### 3. **`with` 的特殊性** ```kotlin val user = User("Alice", 30) with(user) { println("$name - $age") // 直接访问属性 incrementAge() // 调用方法 } // 无返回值 ``` - ⚠️ 非扩展函数,需显式传入对象参数 - ✅ 适合对**已初始化对象**执行多个操作[^1] --- ### 使用场景决策树 ```mermaid graph TD A[需要操作对象?] --> B{需要空安全检查?} B --> |是| C[使用 let] B --> |否| D{需要返回对象本身?} D --> |是| E{需要附加操作?} E --> |是| F[使用 also] E --> |否| G[使用 apply] D --> |否| H{操作多个属性/方法?} H --> |是| I[使用 with/run] H --> |否| J[直接操作] ``` --- ### 实战示例 #### 场景1:空安全转换(`let`最佳) ```kotlin val imageUrl: String? = getImageUrl() // 安全转换 + 空值处理 val imageView = imageUrl?.let { url -> loadImage(url) // 加载图片 } ?: placeholderImage // 空时使用占位图 ``` #### 场景2:对象初始化(`apply`最佳) ```kotlin val dialog = AlertDialog.Builder(this).apply { setTitle("警告") setMessage("确定删除?") setPositiveButton("确定") { _, _ -> deleteItem() } setNegativeButton("取消", null) }.create() // 链式创建配置好的对话框 ``` #### 场景3:链式操作(`also` + `let`组合) ```kotlin File("data.log") .also { println("Writing to ${it.path}") } // 日志记录 .writeText("Hello, Kotlin!") .let { Files.getOwner(it.toPath()) } // 获取所有者 ?.also { owner -> println("Owner: $owner") } // 打印所有者 ``` --- ### 关键差异总结 1. **上下文访问** - `this` 风格(`run`/`apply`/`with`):直接访问属性和方法 - `it` 风格(`let`/`also`):对象作为参数传递,避免命名冲突 2. **返回值** - 返回对象本身:`apply`/`also` → 适合构建器模式 - 返回计算结果:`let`/`run`/`with` → 适合转换操作 3. **空安全** - 仅 `let` 可直接配合安全调用操作符 `?.` 处理可空对象[^3] > 💡 **经验法则**: > - 配置对象 → `apply` > - 添加副作用 → `also` > - 空安全转换 → `let` > - 计算属性 → `run` > - 集中操作 → `with`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值