1. 概述
在 Kotlin 中,let()
是一个非常实用的 作用域函数(Scope Function),它允许我们对某个变量进行变换,生成另一个类型的值。
在本教程中,我们将探讨如何对 多个变量 执行类似 let()
的操作。
2. 问题引入
我们先来看一个简单的 let()
示例:
val str: String? = "hello"
val lengthReport = str?.let { "字符串 [$it] 的长度是: ${it.length}" }
println(lengthReport)
// 输出:字符串 [hello] 的长度是: 5
上面的例子中,let()
用于生成字符串的长度报告。注意 str
是一个可空字符串类型(String?
)。我们希望 **只有在 str
不为 null 时才执行 let()
**,所以使用了 null-safe 调用:str?.let { ... }
。
这是处理可空类型时的常见技巧。但问题来了:标准的 let()
只能处理单个变量。如果我们想对多个变量执行类似的 null-safe 操作,该怎么办?
接下来,我们将先讨论两个变量的情况,再扩展到多个变量。
3. 对两个变量使用 null-safe let
我们来尝试几种方法实现对两个可空变量的 null-safe let
操作。
3.1. 嵌套两个 let 调用
最直接的方式是使用两个嵌套的 let()
调用:
val theName: String? = "Kai"
val theNumber: Int? = 7
val result = theName?.let { name ->
theNumber?.let { num -> "Hi $name, $num 的平方是 ${num * num}" }
}
assertThat(result).isEqualTo("Hi Kai, 7 的平方是 49")
这段代码通过嵌套的方式实现了两个变量的 null-safe 检查和处理。虽然功能没问题,但嵌套结构不够直观,阅读起来略显复杂。
3.2. 自定义 let2()
函数
为了简化代码,我们可以封装一个 let2()
函数,用于处理两个变量的 null-safe 操作:
inline fun <T1 : Any, T2 : Any, R : Any> let2(p1: T1?, p2: T2?, block: (T1, T2) -> R?): R? {
return if (p1 != null && p2 != null) block(p1, p2) else null
}
这个函数接收两个可空参数和一个 lambda 表达式。只有当两个参数都不为 null 时才会执行 lambda。
使用示例:
val nullNum: Int? = null
assertThat(let2("Kai", 7) { name, num -> "Hi $name, $num 的平方是 ${num * num}" }).isEqualTo("Hi Kai, 7 的平方是 49")
assertThat(let2(nullNum, 7) { name, num -> "Hi $name, $num 的平方是 ${num * num}" }).isNull()
assertThat(let2(7, nullNum) { name, num -> "Hi $name, $num 的平方是 ${num * num}" }).isNull()
assertThat(let2(nullNum, nullNum) { name, num -> "Hi $name, $num 的平方是 ${num * num}" }).isNull()
✅ 所有测试通过,说明我们的 let2()
函数可以正常工作。
4. 对多个变量使用 null-safe let
接下来我们看看如何扩展到三个变量甚至更多变量的 null-safe 操作。
4.1. 扩展 let2()
为 let3()
我们只需在 let2()
的基础上增加一个参数即可得到 let3()
:
inline fun <T1 : Any, T2 : Any, T3 : Any, R : Any> let3(p1: T1?, p2: T2?, p3: T3?, block: (T1, T2, T3) -> R?): R? {
return if (p1 != null && p2 != null && p3 != null) block(p1, p2, p3) else null
}
测试示例:
assertThat(let3(5, 6, 7) { n1, n2, n3 -> "$n1 + $n2 + $n3 是 ${n1 + n2 + n3}" }).isEqualTo("5 + 6 + 7 是 18")
assertThat(let3(nullNum, 7, 6) { n1, n2, n3 -> "$n1 + $n2 + $n3 是 ${n1 + n2 + n3}" }).isNull()
assertThat(let3(nullNum, nullNum, 6) { n1, n2, n3 -> "$n1 + $n2 + $n3 是 ${n1 + n2 + n3}" }).isNull()
assertThat(let3(nullNum, nullNum, nullNum) { n1, n2, n3 -> "$n1 + $n2 + $n3 是 ${n1 + n2 + n3}" }).isNull()
✅ 测试通过,说明 let3()
也能正常工作。
但问题又来了:如果变量数量不固定怎么办?难道我们要写 let4()
、let5()
、let6()
... 吗?
4.2. 使用 vararg 实现动态数量参数的 let
Kotlin 中的 vararg
允许我们传入可变数量的参数。我们可以基于此创建一个通用函数:
inline fun <T : Any, R : Any> letIfAllNotNull(vararg arguments: T?, block: (List<T>) -> R): R? {
return if (arguments.all { it != null }) {
block(arguments.filterNotNull())
} else null
}
这个函数会在所有参数都非 null 时执行 lambda,并将参数以 List<T>
的形式传入。
使用示例:
assertThat(letIfAllNotNull(5, 6, 7, 8) { "${it.joinToString(separator = " + ") { num -> "$num" }} 是 ${it.sum()}" }).isEqualTo("5 + 6 + 7 + 8 是 26")
assertThat(letIfAllNotNull(5, 6, 7) { "${it.joinToString(separator = " + ") { num -> "$num" }} 是 ${it.sum()}" }).isEqualTo("5 + 6 + 7 是 18")
assertThat(letIfAllNotNull(5, 7) { "${it.joinToString(separator = " + ") { num -> "$num" }} 是 ${it.sum()}" }).isEqualTo("5 + 7 是 12")
assertThat(letIfAllNotNull(nullNum, 7, 6) { "${it.joinToString(separator = " + ") { num -> "$num" }} 是 ${it.sum()}" }).isNull()
assertThat(letIfAllNotNull(nullNum, null, 6) { "${it.joinToString(separator = " + ") { num -> "$num" }} 是 ${it.sum()}" }).isNull()
assertThat(letIfAllNotNull(nullNum, nullNum, nullNum) { "${it.joinToString(separator = " + ") { num -> "$num" }} 是 ${it.sum()}" }).isNull()
✅ 测试通过,说明该函数可以灵活处理任意数量的可空参数。
4.3. 对任意非 null 变量执行 let
如果我们希望 只要有一个变量非 null 就执行 lambda,我们可以对上面的函数稍作修改:
inline fun <T : Any, R : Any> letIfAnyNotNull(vararg arguments: T?, block: (List<T>) -> R?): R? {
return if (arguments.any { it != null }) {
block(arguments.filterNotNull())
} else null
}
测试示例:
assertThat(letIfAnyNotNull(5, 6, 7) { "${it.joinToString(separator = " + ") { num -> "$num" }} 是 ${it.sum()}" }).isEqualTo("5 + 6 + 7 是 18")
assertThat(letIfAnyNotNull(nullNum, 6, 7) { "${it.joinToString(separator = " + ") { num -> "$num" }} 是 ${it.sum()}" }).isEqualTo("6 + 7 是 13")
assertThat(letIfAnyNotNull(nullNum, nullNum, 7) { "${it.joinToString(separator = " + ") { num -> "$num" }} 是 ${it.sum()}" }).isEqualTo("7 是 7")
assertThat(letIfAnyNotNull(nullNum, nullNum, nullNum) { "${it.joinToString(separator = " + ") { num -> "$num" }} 是 ${it.sum()}" }).isNull()
✅ 测试通过,说明 letIfAnyNotNull()
可以灵活处理部分参数为 null 的情况。
5. 总结
在本教程中,我们探讨了如何在 Kotlin 中对多个变量执行 null-safe 的 let()
操作。总结如下:
✅ 解决方案对比:
方法 | 支持变量数 | null 检查方式 | 是否推荐 |
---|---|---|---|
嵌套 let() |
固定(2个) | 所有变量非 null | ❌ 阅读复杂 |
let2() / let3() |
固定(2或3个) | 所有变量非 null | ✅ 简洁清晰 |
letIfAllNotNull() |
动态 | 所有变量非 null | ✅ 推荐 |
letIfAnyNotNull() |
动态 | 至少一个非 null | ✅ 特殊场景适用 |
如果你的项目中经常需要处理多个可空变量的转换逻辑,建议封装一个通用函数,如 letIfAllNotNull()
或 letIfAnyNotNull()
,以提高代码可读性和复用性。
完整代码示例请参考:GitHub 示例项目 ✅
💡 踩坑提醒:
使用vararg
时注意参数类型必须一致,否则需要显式指定类型。let()
返回类型取决于 lambda 返回值,务必注意 null 处理逻辑。