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 处理逻辑。


原始标题:Multiple Variables “let” in Kotlin