Kotlin 标准库函数详解:let、apply、run、with、also、takeIf、takeUnless
本文详细解析 Kotlin 标准库中的几个核心作用域函数(scope functions),包括
let、apply、run、with、also、takeIf和takeUnless。通过对比它们的定义、参数、返回值和典型使用场景,帮助开发者更好地理解和运用这些函数。
博主博客
前提:Kotlin 的尾随闭包语法
Kotlin 支持尾随闭包(trailing lambda)语法:如果函数的最后一个参数是闭包,则该闭包可以写在括号外部;如果函数只有这一个参数,括号可以省略。
// 正常写法
button.setOnClickListener({
Toast.makeText(context, "test", Toast.LENGTH_SHORT).show()
})
// 使用尾随闭包语法(推荐)
button.setOnClickListener {
Toast.makeText(context, "test", Toast.LENGTH_SHORT).show()
}
本文介绍的所有函数都使用了这一语法特性。
各函数详解
1. repeat
定义:一个独立函数,非扩展函数。
作用:将指定操作重复执行指定次数。
闭包参数:当前迭代的索引(从0开始)。
返回值:Unit。
public inline fun repeat(times: Int, action: (Int) -> Unit) {
for (index in 0 until times) {
action(index)
}
}
示例:
repeat(3) { index ->
println("Hello world $index")
}
// 输出:
// Hello world 0
// Hello world 1
// Hello world 2
2. with
定义:一个独立函数,非扩展函数。
作用:将指定对象作为闭包的接收者(receiver)执行操作,并返回闭包的执行结果。
闭包参数:隐式接收者 this(可直接访问对象成员)。
返回值:闭包的最后一行结果。
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
return receiver.block()
}
示例:
val list = with(ArrayList<String>()) {
add("item1")
add("item2")
this // 返回列表本身
}
println(list) // 输出:[item1, item2]
3. let
定义:扩展函数。
作用:将调用对象作为闭包参数 it 执行操作,并返回闭包的执行结果。
闭包参数:调用对象本身作为 it。
返回值:闭包的最后一行结果。
public inline fun <T, R> T.let(block: (T) -> R): R {
return block(this)
}
示例:
val length = "Hello".let {
println("字符串是:$it")
it.length // 返回长度
}
println("长度:$length")
// 输出:
// 字符串是:Hello
// 长度:5
4. apply
定义:扩展函数。
作用:将调用对象作为闭包接收者执行操作,返回对象本身。
闭包参数:隐式接收者 this。
返回值:调用对象本身(this)。
public inline fun <T> T.apply(block: T.() -> Unit): T {
block()
return this
}
示例:
val list = ArrayList<String>().apply {
add("apply1")
add("apply2")
println("初始化完成:$this")
} // list 就是刚刚初始化的 ArrayList
println("外部访问:$list")
5. run(扩展函数版本)
定义:扩展函数。
作用:将调用对象作为闭包接收者执行操作,并返回闭包的执行结果。
闭包参数:隐式接收者 this。
返回值:闭包的最后一行结果。
public inline fun <T, R> T.run(block: T.() -> R): R {
return block()
}
示例:
val result = "Hello".run {
println("接收者是:$this")
this.length // 返回长度
}
println("结果:$result")
6. run(非扩展函数版本)
定义:独立函数,非扩展函数。
作用:执行一个闭包,并返回其执行结果。
闭包参数:无参数。
返回值:闭包的最后一行结果。
public inline fun <R> run(block: () -> R): R {
return block()
}
示例:
val date = run {
val now = System.currentTimeMillis()
Date(now) // 返回新创建的 Date 对象
}
println("当前时间:$date")
7. also
定义:扩展函数(Kotlin 1.1+)。
作用:将调用对象作为闭包参数 it 执行操作,返回对象本身。
闭包参数:调用对象本身作为 it。
返回值:调用对象本身(this)。
public inline fun <T> T.also(block: (T) -> Unit): T {
block(this)
return this
}
示例:
val list = ArrayList<String>().also {
it.add("also1")
it.add("also2")
println("also 中间操作:$it")
} // list 就是刚刚修改的 ArrayList
println("最终结果:$list")
8. takeIf
定义:扩展函数(Kotlin 1.1+)。
作用:如果对象满足给定条件(闭包返回 true),则返回该对象,否则返回 null。
闭包参数:调用对象本身作为 it。
返回值:调用对象本身 或 null。
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
return if (predicate(this)) this else null
}
示例:
val date = Date().takeIf {
// 检查是否在 2018 年元旦之后
it.after(Date(2018 - 1900, 0, 1))
}
println("符合条件的日期:$date") // 如果当前时间晚于 2018-01-01,则输出日期,否则输出 null
9. takeUnless
定义:扩展函数(Kotlin 1.1+)。
作用:与 takeIf 相反,如果对象不满足给定条件(闭包返回 false),则返回该对象,否则返回 null。
闭包参数:调用对象本身作为 it。
返回值:调用对象本身 或 null。
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
return if (!predicate(this)) this else null
}
示例:
val date = Date().takeUnless {
// 检查是否在 2018 年元旦之后
it.after(Date(2018 - 1900, 0, 1))
}
println("不符合条件的日期:$date") // 如果当前时间早于 2018-01-01,则输出日期,否则输出 null
总结对比表
| 函数名 | 定义 | 闭包参数 | 闭包返回值 | 函数返回值 | 是否为扩展函数 | 特点 |
|---|---|---|---|---|---|---|
| repeat | repeat(times: Int, action: (Int) -> Unit) |
索引值 | Unit | Unit | 否 | 循环执行 |
| with | with(receiver: T, block: T.() -> R): R |
this(隐式) |
任意类型 | 闭包返回值 | 否 | 非扩展,需显式传入对象 |
| let | T.let(block: (T) -> R): R |
it(显式) |
任意类型 | 闭包返回值 | 是 | 常用于空安全链式调用 |
| apply | T.apply(block: T.() -> Unit): T |
this(隐式) |
Unit | 对象本身(this) | 是 | 对象初始化配置 |
| run(扩展) | T.run(block: T.() -> R): R |
this(隐式) |
任意类型 | 闭包返回值 | 是 | 结合了 with 和 let 的特点 |
| run(非扩展) | run(block: () -> R): R |
无 | 任意类型 | 闭包返回值 | 否 | 创建一个独立作用域 |
| also | T.also(block: (T) -> Unit): T |
it(显式) |
Unit | 对象本身(this) | 是 | 执行副作用,返回原对象 |
| takeIf | T.takeIf(predicate: (T) -> Boolean): T? |
it(显式) |
Boolean | 对象本身 或 null | 是 | 条件判断并返回原对象 |
| takeUnless | T.takeUnless(predicate: (T) -> Boolean): T? |
it(显式) |
Boolean | 对象本身 或 null | 是 | takeIf 的反向操作 |
如何选择合适的作用域函数?
根据你的需求,遵循以下决策路径:
-
是否需要返回对象本身?
-
是 → 选择
apply或also- 需要访问对象成员(使用
this)? →apply - 需要将对象作为参数(使用
it)? →also
- 需要访问对象成员(使用
-
否 → 进入步骤 2
-
-
是否需要将对象作为参数传递(使用
it)?-
是 → 选择
let、takeIf或takeUnless- 需要条件判断?
- 条件为真时返回对象 →
takeIf - 条件为假时返回对象 →
takeUnless
- 条件为真时返回对象 →
- 不需要条件判断 →
let
- 需要条件判断?
-
否 → 进入步骤 3
-
-
是否需要将对象作为接收者(使用
this)?-
是 → 选择
run(扩展版本)或with- 对象可能为 null? →
run - 对象不为 null? →
with
- 对象可能为 null? →
-
否 → 选择
run(非扩展版本)(仅需执行代码块)
-
综合示例
示例 1:对象初始化
data class User(
var id: Int = 0,
var name: String? = null,
var hobbies: List<String>? = null
)
// 1. 传统方式
val user1 = User()
user1.id = 1
user1.name = "张三"
user1.hobbies = listOf("篮球", "音乐")
// 2. 使用 apply(推荐:初始化配置)
val user2 = User().apply {
id = 2
name = "李四"
hobbies = listOf("阅读", "旅行")
}
// 3. 使用 also(执行额外操作)
val user3 = User().also {
it.id = 3
it.name = "王五"
}.also {
println("用户 ${it.name} 已创建")
}
// 4. 使用 let(转换数据)
val userId = User().let { user ->
user.id = 4
user.name = "赵六"
user.id // 返回 id
}
示例 2:空安全与条件处理
data class Resp<T>(
var code: Int = 0,
var body: T? = null,
var errorMessage: String? = null
) {
fun isSuccess() = code == 200
}
// 传统 null 检查方式
fun processRespTraditional(resp: Resp<String>?) {
if (resp != null) {
if (resp.isSuccess()) {
println("成功:${resp.body}")
} else {
println("失败:${resp.errorMessage}")
}
}
}
// 使用作用域函数的优雅方式
fun processRespModern(resp: Resp<String>?) {
// 1. 使用 run(访问成员方便)
resp?.run {
if (isSuccess()) {
println("成功:$body")
} else {
println("失败:$errorMessage")
}
}
// 2. 使用 let(需要 it 参数)
resp?.let {
if (it.isSuccess()) {
println("成功:${it.body}")
} else {
println("失败:${it.errorMessage}")
}
}
// 3. 使用 takeIf 进行条件筛选
resp?.takeIf { it.isSuccess() }?.let {
println("只处理成功情况:${it.body}")
}
resp?.takeUnless { it.isSuccess() }?.let {
println("只处理失败情况:${it.errorMessage}")
}
}
示例 3:链式调用
// 链式调用多个作用域函数
val result = "hello world"
.takeIf { it.length > 5 } // 1. 长度大于5则保留字符串
?.let { it.toUpperCase() } // 2. 转为大写
?.apply { println("中间值:$this") } // 3. 打印中间结果(返回原字符串)
?.run { this.split(" ") } // 4. 分割为列表
?.also { println("最终列表:$it") } // 5. 打印最终结果(返回原列表)
println(result) // 输出:["HELLO", "WORLD"]
实用技巧与注意事项
-
避免过度嵌套:链式调用虽然强大,但过度嵌套会降低可读性
// 不推荐:嵌套过深 user?.let { user -> user.name?.let { name -> name.takeIf { it.length > 3 }?.let { println(it) } } } // 推荐:使用多个链式调用 user?.name ?.takeIf { it.length > 3 } ?.let { println(it) } -
明确使用
it还是this:- 当对象的主要目的是作为参数处理时,使用
it(let、also) - 当对象的主要目的是调用其成员时,使用
this(apply、run、with)
- 当对象的主要目的是作为参数处理时,使用
-
基于返回值选择函数:
- 需要继续操作原对象 →
apply、also - 需要获取计算结果 →
let、run、with - 需要条件过滤 →
takeIf、takeUnless
- 需要继续操作原对象 →
-
性能考虑:所有这些都是内联函数,运行时没有额外开销,可以放心使用。
结论
Kotlin 的作用域函数提供了强大的表达能力,能够让代码更加简洁、安全且富有表现力。掌握这些函数的区别和适用场景,能够显著提升 Kotlin 编程效率和代码质量。记住,选择哪个函数取决于你的具体需求:是否需要返回对象本身?是否需要将对象作为参数?理解了这一点,就能轻松选出最合适的工具。
Kotlin 标准库函数详解:let、apply、run、with、also、takeIf、takeUnless
https://blog.uso6.com/archives/kotlin-biao-zhun-ku-han-shu-xiang-jie-let-apply-run-with-also-takeif-takeunless
评论