Kotlin 中的多变量 let
Multiple variable let in Kotlin
有什么方法可以在 kotlin 中为多个可空变量链接多个 let 吗?
fun example(first: String?, second: String?) {
first?.let {
second?.let {
// Do something just if both are != null
}
}
}
我的意思是,像这样:
fun example(first: String?, second: String?) {
first?.let && second?.let {
// Do something just if both are != null
}
}
您可以为此编写自己的函数:
fun <T, U, R> Pair<T?, U?>.biLet(body: (T, U) -> R): R? {
val first = first
val second = second
if (first != null && second != null) {
return body(first, second)
}
return null
}
(first to second).biLet { first, second ->
// body
}
您可以创建一个 arrayIfNoNulls
函数:
fun <T : Any> arrayIfNoNulls(vararg elements: T?): Array<T>? {
if (null in elements) {
return null
}
@Suppress("UNCHECKED_CAST")
return elements as Array<T>
}
然后您可以将它用于可变数量的值 let
:
fun example(first: String?, second: String?) {
arrayIfNoNulls(first, second)?.let { (first, second) ->
// Do something if each element is not null
}
}
如果您已经有一个数组,您可以创建一个 takeIfNoNulls
函数(灵感来自 takeIf
and requireNoNulls
):
fun <T : Any> Array<T?>.takeIfNoNulls(): Array<T>? {
if (null in this) {
return null
}
@Suppress("UNCHECKED_CAST")
return this as Array<T>
}
示例:
array?.takeIfNoNulls()?.let { (first, second) ->
// Do something if each element is not null
}
这里有一些变体,具体取决于您要使用的样式,如果您拥有相同或不同类型的所有物品,以及是否列出未知数量的项目...
混合类型,都不能为null才能计算出新值
对于混合类型,您可以为每个参数计数构建一系列函数,这可能看起来很愚蠢,但对于混合类型来说效果很好:
inline fun <T1: Any, T2: Any, R: Any> safeLet(p1: T1?, p2: T2?, block: (T1, T2)->R?): R? {
return if (p1 != null && p2 != null) block(p1, p2) else null
}
inline fun <T1: Any, T2: Any, T3: Any, R: Any> safeLet(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
}
inline fun <T1: Any, T2: Any, T3: Any, T4: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, p4: T4?, block: (T1, T2, T3, T4)->R?): R? {
return if (p1 != null && p2 != null && p3 != null && p4 != null) block(p1, p2, p3, p4) else null
}
inline fun <T1: Any, T2: Any, T3: Any, T4: Any, T5: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, p4: T4?, p5: T5?, block: (T1, T2, T3, T4, T5)->R?): R? {
return if (p1 != null && p2 != null && p3 != null && p4 != null && p5 != null) block(p1, p2, p3, p4, p5) else null
}
// ...keep going up to the parameter count you care about
用法示例:
val risk = safeLet(person.name, person.age) { name, age ->
// do something
}
当列表没有空项时执行代码块
这里有两种方式,第一种是当列表包含所有非空项时执行代码块,第二种是当列表至少包含一个非空项时执行相同的代码块。这两种情况都将非空项列表传递给代码块:
函数:
fun <T: Any, R: Any> Collection<T?>.whenAllNotNull(block: (List<T>)->R) {
if (this.all { it != null }) {
block(this.filterNotNull()) // or do unsafe cast to non null collectino
}
}
fun <T: Any, R: Any> Collection<T?>.whenAnyNotNull(block: (List<T>)->R) {
if (this.any { it != null }) {
block(this.filterNotNull())
}
}
用法示例:
listOf("something", "else", "matters").whenAllNotNull {
println(it.joinToString(" "))
} // output "something else matters"
listOf("something", null, "matters").whenAllNotNull {
println(it.joinToString(" "))
} // no output
listOf("something", null, "matters").whenAnyNotNull {
println(it.joinToString(" "))
} // output "something matters"
让函数接收项目列表并执行相同操作的细微变化:
fun <T: Any, R: Any> whenAllNotNull(vararg options: T?, block: (List<T>)->R) {
if (options.all { it != null }) {
block(options.filterNotNull()) // or do unsafe cast to non null collection
}
}
fun <T: Any, R: Any> whenAnyNotNull(vararg options: T?, block: (List<T>)->R) {
if (options.any { it != null }) {
block(options.filterNotNull())
}
}
用法示例:
whenAllNotNull("something", "else", "matters") {
println(it.joinToString(" "))
} // output "something else matters"
这些变体可以更改为具有 return 值,例如 let()
。
使用第一个非空项(合并)
类似于 SQL 合并函数,return 第一个非空项。函数的两种风格:
fun <T: Any> coalesce(vararg options: T?): T? = options.firstOrNull { it != null }
fun <T: Any> Collection<T?>.coalesce(): T? = this.firstOrNull { it != null }
用法示例:
coalesce(null, "something", null, "matters")?.let {
it.length
} // result is 9, length of "something"
listOf(null, "something", null, "matters").coalesce()?.let {
it.length
} // result is 9, length of "something"
其他变化
...还有其他变体,但随着规格的增加,可以缩小范围。
实际上,你可以简单地这样做,你知道吗? ;)
if (first != null && second != null) {
// your logic here...
}
在 Kotlin 中使用正常的 null 检查没有错。
而且对于将要查看您的代码的每个人来说,它的可读性要高得多。
对于仅检查两个值且不必使用列表的情况:
fun <T1, T2> ifNotNull(value1: T1?, value2: T2?, bothNotNull: (T1, T2) -> (Unit)) {
if (value1 != null && value2 != null) {
bothNotNull(value1, value2)
}
}
用法示例:
var firstString: String?
var secondString: String?
ifNotNull(firstString, secondString) { first, second -> Log.d(TAG, "$first, $second") }
对于要检查的任意数量的值,您可以使用此:
fun checkNulls(vararg elements: Any?, block: (Array<*>) -> Unit) {
elements.forEach { if (it == null) return }
block(elements.requireNoNulls())
}
并且会这样使用:
val dada: String? = null
val dede = "1"
checkNulls(dada, dede) { strings ->
}
发送到块的元素正在使用通配符,如果你想访问值,你需要检查类型,如果你只需要使用一种类型,你可以将其改为泛型
我实际上更喜欢使用以下辅助函数来解决它:
fun <A, B> T(tuple: Pair<A?, B?>): Pair<A, B>? =
if(tuple.first == null || tuple.second == null) null
else Pair(tuple.first!!, tuple.second!!)
fun <A, B, C> T(tuple: Triple<A?, B?, C?>): Triple<A, B, C>? =
if(tuple.first == null || tuple.second == null || tuple.third == null) null
else Triple(tuple.first!!, tuple.second!!, tuple.third!!)
fun <A, B> T(first: A?, second: B?): Pair<A, B>? =
if(first == null || second == null) null
else Pair(first, second)
fun <A, B, C> T(first: A?, second: B?, third: C?): Triple<A, B, C>? =
if(first == null || second == null || third == null) null
else Triple(first, second, third)
下面是您应该如何使用它们:
val a: A? = someValue
val b: B? = someOtherValue
T(a, b)?.let { (a, b) ->
// Shadowed a and b are of type a: A and b: B
val c: C? = anotherValue
T(a, b, c)
}?.let { (a, b, c) ->
// Shadowed a, b and c are of type a: A, b: B and c: C
.
.
.
}
如果有兴趣,这里有两个我用来解决这个问题的函数。
inline fun <T: Any> guardLet(vararg elements: T?, closure: () -> Nothing): List<T> {
return if (elements.all { it != null }) {
elements.filterNotNull()
} else {
closure()
}
}
inline fun <T: Any> ifLet(vararg elements: T?, closure: (List<T>) -> Unit) {
if (elements.all { it != null }) {
closure(elements.filterNotNull())
}
}
用法:
// Will print
val (first, second, third) = guardLet("Hello", 3, Thing("Hello")) { return }
println(first)
println(second)
println(third)
// Will return
val (first, second, third) = guardLet("Hello", null, Thing("Hello")) { return }
println(first)
println(second)
println(third)
// Will print
ifLet("Hello", "A", 9) {
(first, second, third) ->
println(first)
println(second)
println(third)
}
// Won't print
ifLet("Hello", 9, null) {
(first, second, third) ->
println(first)
println(second)
println(third)
}
我通过创建一些函数来解决这个问题,这些函数或多或少地复制了 with 的行为,但是接受多个参数并且只调用所有参数都是非空的函数。
fun <R, A, B> withNoNulls(p1: A?, p2: B?, function: (p1: A, p2: B) -> R): R? = p1?.let { p2?.let { function.invoke(p1, p2) } }
fun <R, A, B, C> withNoNulls(p1: A?, p2: B?, p3: C?, function: (p1: A, p2: B, p3: C) -> R): R? = p1?.let { p2?.let { p3?.let { function.invoke(p1, p2, p3) } } }
fun <R, A, B, C, D> withNoNulls(p1: A?, p2: B?, p3: C?, p4: D?, function: (p1: A, p2: B, p3: C, p4: D) -> R): R? = p1?.let { p2?.let { p3?.let { p4?.let { function.invoke(p1, p2, p3, p4) } } } }
fun <R, A, B, C, D, E> withNoNulls(p1: A?, p2: B?, p3: C?, p4: D?, p5: E?, function: (p1: A, p2: B, p3: C, p4: D, p5: E) -> R): R? = p1?.let { p2?.let { p3?.let { p4?.let { p5?.let { function.invoke(p1, p2, p3, p4, p5) } } } } }
然后我这样使用它:
withNoNulls("hello", "world", Throwable("error")) { p1, p2, p3 ->
p3.printStackTrace()
p1.plus(" ").plus(p2)
}?.let {
Log.d("TAG", it)
} ?: throw Exception("One or more parameters was null")
这个明显的问题是我必须为我需要的每个案例(变量数量)定义一个函数,但至少我认为使用它们时代码看起来很干净。
你也可以这样做
if (listOfNotNull(var1, var2, var3).size == 3) {
// All variables are non-null
}
我对预期答案进行了一些升级:
inline fun <T: Any, R: Any> ifLet(vararg elements: T?, closure: (List<T>) -> R): R? {
return if (elements.all { it != null }) {
closure(elements.filterNotNull())
} else null
}
这使这成为可能:
iflet("first", "sconed") {
// do somehing
} ?: run {
// do this if one of the params are null
}
基于@yole
回答的另一个想法
fun <T, U, R> Pair<T?, U?>.toLet(body: (List<*>) -> R): R? {
val one = first
val two = second
if (one == null || two == null)
return null
return if (one is Pair<*, *>) {
one.toLet { a ->
body(listOf(a, listOf(two)).flatten())
}
} else {
body(listOf(one, two))
}
}
因此您可以执行以下操作
(1 to 6 to "a" to 4.5).toLet { (a, b, c, d) ->
// Rest of code
}
也许有点晚了。但现在它存在一个库来满足这一特定需求。是Konad; have look at the maybe section
我将在此处报告来自文档的示例用法:
val foo: Int? = 1
val bar: String? = "2"
val baz: Float? = 3.0f
fun useThem(x: Int, y: String, z: Float): Int = x + y.toInt() + z.toInt()
val result: Int? = ::useThem.curry()
.on(foo.maybe)
.on(bar.maybe)
.on(baz.maybe)
.nullable
// or even
val result: Result<Int> = ::useThem.curry()
.on(foo.ifNull("Foo should not be null"))
.on(bar.ifNull("Bar should not be null"))
.on(baz.ifNull("Baz should not be null"))
.result
我喜欢使用列表过滤空值的想法,当我处理相同类型时我通常会做类似的事情,但是当有多种类型时,为了避免将值解析为 Any
, 我就是这么干的
fun someFunction() {
val value1: String = this.value1 ?: return
val value2: Int = this.value2 ?: return
...
}
它有效,对我来说保持类型安全很重要
使用内置的 kotlin 功能而不添加任何额外的辅助函数来修复此问题的最直接方法如下:
fun example(first: String?, second: String?) {
first?.let { itFirst ->
second?.let { itSecond ->
// Do something just if both are != null
// itFirst & itSecond are both != null if this block is reached
}
}
}
有什么方法可以在 kotlin 中为多个可空变量链接多个 let 吗?
fun example(first: String?, second: String?) {
first?.let {
second?.let {
// Do something just if both are != null
}
}
}
我的意思是,像这样:
fun example(first: String?, second: String?) {
first?.let && second?.let {
// Do something just if both are != null
}
}
您可以为此编写自己的函数:
fun <T, U, R> Pair<T?, U?>.biLet(body: (T, U) -> R): R? {
val first = first
val second = second
if (first != null && second != null) {
return body(first, second)
}
return null
}
(first to second).biLet { first, second ->
// body
}
您可以创建一个 arrayIfNoNulls
函数:
fun <T : Any> arrayIfNoNulls(vararg elements: T?): Array<T>? {
if (null in elements) {
return null
}
@Suppress("UNCHECKED_CAST")
return elements as Array<T>
}
然后您可以将它用于可变数量的值 let
:
fun example(first: String?, second: String?) {
arrayIfNoNulls(first, second)?.let { (first, second) ->
// Do something if each element is not null
}
}
如果您已经有一个数组,您可以创建一个 takeIfNoNulls
函数(灵感来自 takeIf
and requireNoNulls
):
fun <T : Any> Array<T?>.takeIfNoNulls(): Array<T>? {
if (null in this) {
return null
}
@Suppress("UNCHECKED_CAST")
return this as Array<T>
}
示例:
array?.takeIfNoNulls()?.let { (first, second) ->
// Do something if each element is not null
}
这里有一些变体,具体取决于您要使用的样式,如果您拥有相同或不同类型的所有物品,以及是否列出未知数量的项目...
混合类型,都不能为null才能计算出新值
对于混合类型,您可以为每个参数计数构建一系列函数,这可能看起来很愚蠢,但对于混合类型来说效果很好:
inline fun <T1: Any, T2: Any, R: Any> safeLet(p1: T1?, p2: T2?, block: (T1, T2)->R?): R? {
return if (p1 != null && p2 != null) block(p1, p2) else null
}
inline fun <T1: Any, T2: Any, T3: Any, R: Any> safeLet(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
}
inline fun <T1: Any, T2: Any, T3: Any, T4: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, p4: T4?, block: (T1, T2, T3, T4)->R?): R? {
return if (p1 != null && p2 != null && p3 != null && p4 != null) block(p1, p2, p3, p4) else null
}
inline fun <T1: Any, T2: Any, T3: Any, T4: Any, T5: Any, R: Any> safeLet(p1: T1?, p2: T2?, p3: T3?, p4: T4?, p5: T5?, block: (T1, T2, T3, T4, T5)->R?): R? {
return if (p1 != null && p2 != null && p3 != null && p4 != null && p5 != null) block(p1, p2, p3, p4, p5) else null
}
// ...keep going up to the parameter count you care about
用法示例:
val risk = safeLet(person.name, person.age) { name, age ->
// do something
}
当列表没有空项时执行代码块
这里有两种方式,第一种是当列表包含所有非空项时执行代码块,第二种是当列表至少包含一个非空项时执行相同的代码块。这两种情况都将非空项列表传递给代码块:
函数:
fun <T: Any, R: Any> Collection<T?>.whenAllNotNull(block: (List<T>)->R) {
if (this.all { it != null }) {
block(this.filterNotNull()) // or do unsafe cast to non null collectino
}
}
fun <T: Any, R: Any> Collection<T?>.whenAnyNotNull(block: (List<T>)->R) {
if (this.any { it != null }) {
block(this.filterNotNull())
}
}
用法示例:
listOf("something", "else", "matters").whenAllNotNull {
println(it.joinToString(" "))
} // output "something else matters"
listOf("something", null, "matters").whenAllNotNull {
println(it.joinToString(" "))
} // no output
listOf("something", null, "matters").whenAnyNotNull {
println(it.joinToString(" "))
} // output "something matters"
让函数接收项目列表并执行相同操作的细微变化:
fun <T: Any, R: Any> whenAllNotNull(vararg options: T?, block: (List<T>)->R) {
if (options.all { it != null }) {
block(options.filterNotNull()) // or do unsafe cast to non null collection
}
}
fun <T: Any, R: Any> whenAnyNotNull(vararg options: T?, block: (List<T>)->R) {
if (options.any { it != null }) {
block(options.filterNotNull())
}
}
用法示例:
whenAllNotNull("something", "else", "matters") {
println(it.joinToString(" "))
} // output "something else matters"
这些变体可以更改为具有 return 值,例如 let()
。
使用第一个非空项(合并)
类似于 SQL 合并函数,return 第一个非空项。函数的两种风格:
fun <T: Any> coalesce(vararg options: T?): T? = options.firstOrNull { it != null }
fun <T: Any> Collection<T?>.coalesce(): T? = this.firstOrNull { it != null }
用法示例:
coalesce(null, "something", null, "matters")?.let {
it.length
} // result is 9, length of "something"
listOf(null, "something", null, "matters").coalesce()?.let {
it.length
} // result is 9, length of "something"
其他变化
...还有其他变体,但随着规格的增加,可以缩小范围。
实际上,你可以简单地这样做,你知道吗? ;)
if (first != null && second != null) {
// your logic here...
}
在 Kotlin 中使用正常的 null 检查没有错。
而且对于将要查看您的代码的每个人来说,它的可读性要高得多。
对于仅检查两个值且不必使用列表的情况:
fun <T1, T2> ifNotNull(value1: T1?, value2: T2?, bothNotNull: (T1, T2) -> (Unit)) {
if (value1 != null && value2 != null) {
bothNotNull(value1, value2)
}
}
用法示例:
var firstString: String?
var secondString: String?
ifNotNull(firstString, secondString) { first, second -> Log.d(TAG, "$first, $second") }
对于要检查的任意数量的值,您可以使用此:
fun checkNulls(vararg elements: Any?, block: (Array<*>) -> Unit) {
elements.forEach { if (it == null) return }
block(elements.requireNoNulls())
}
并且会这样使用:
val dada: String? = null
val dede = "1"
checkNulls(dada, dede) { strings ->
}
发送到块的元素正在使用通配符,如果你想访问值,你需要检查类型,如果你只需要使用一种类型,你可以将其改为泛型
我实际上更喜欢使用以下辅助函数来解决它:
fun <A, B> T(tuple: Pair<A?, B?>): Pair<A, B>? =
if(tuple.first == null || tuple.second == null) null
else Pair(tuple.first!!, tuple.second!!)
fun <A, B, C> T(tuple: Triple<A?, B?, C?>): Triple<A, B, C>? =
if(tuple.first == null || tuple.second == null || tuple.third == null) null
else Triple(tuple.first!!, tuple.second!!, tuple.third!!)
fun <A, B> T(first: A?, second: B?): Pair<A, B>? =
if(first == null || second == null) null
else Pair(first, second)
fun <A, B, C> T(first: A?, second: B?, third: C?): Triple<A, B, C>? =
if(first == null || second == null || third == null) null
else Triple(first, second, third)
下面是您应该如何使用它们:
val a: A? = someValue
val b: B? = someOtherValue
T(a, b)?.let { (a, b) ->
// Shadowed a and b are of type a: A and b: B
val c: C? = anotherValue
T(a, b, c)
}?.let { (a, b, c) ->
// Shadowed a, b and c are of type a: A, b: B and c: C
.
.
.
}
如果有兴趣,这里有两个我用来解决这个问题的函数。
inline fun <T: Any> guardLet(vararg elements: T?, closure: () -> Nothing): List<T> {
return if (elements.all { it != null }) {
elements.filterNotNull()
} else {
closure()
}
}
inline fun <T: Any> ifLet(vararg elements: T?, closure: (List<T>) -> Unit) {
if (elements.all { it != null }) {
closure(elements.filterNotNull())
}
}
用法:
// Will print
val (first, second, third) = guardLet("Hello", 3, Thing("Hello")) { return }
println(first)
println(second)
println(third)
// Will return
val (first, second, third) = guardLet("Hello", null, Thing("Hello")) { return }
println(first)
println(second)
println(third)
// Will print
ifLet("Hello", "A", 9) {
(first, second, third) ->
println(first)
println(second)
println(third)
}
// Won't print
ifLet("Hello", 9, null) {
(first, second, third) ->
println(first)
println(second)
println(third)
}
我通过创建一些函数来解决这个问题,这些函数或多或少地复制了 with 的行为,但是接受多个参数并且只调用所有参数都是非空的函数。
fun <R, A, B> withNoNulls(p1: A?, p2: B?, function: (p1: A, p2: B) -> R): R? = p1?.let { p2?.let { function.invoke(p1, p2) } }
fun <R, A, B, C> withNoNulls(p1: A?, p2: B?, p3: C?, function: (p1: A, p2: B, p3: C) -> R): R? = p1?.let { p2?.let { p3?.let { function.invoke(p1, p2, p3) } } }
fun <R, A, B, C, D> withNoNulls(p1: A?, p2: B?, p3: C?, p4: D?, function: (p1: A, p2: B, p3: C, p4: D) -> R): R? = p1?.let { p2?.let { p3?.let { p4?.let { function.invoke(p1, p2, p3, p4) } } } }
fun <R, A, B, C, D, E> withNoNulls(p1: A?, p2: B?, p3: C?, p4: D?, p5: E?, function: (p1: A, p2: B, p3: C, p4: D, p5: E) -> R): R? = p1?.let { p2?.let { p3?.let { p4?.let { p5?.let { function.invoke(p1, p2, p3, p4, p5) } } } } }
然后我这样使用它:
withNoNulls("hello", "world", Throwable("error")) { p1, p2, p3 ->
p3.printStackTrace()
p1.plus(" ").plus(p2)
}?.let {
Log.d("TAG", it)
} ?: throw Exception("One or more parameters was null")
这个明显的问题是我必须为我需要的每个案例(变量数量)定义一个函数,但至少我认为使用它们时代码看起来很干净。
你也可以这样做
if (listOfNotNull(var1, var2, var3).size == 3) {
// All variables are non-null
}
我对预期答案进行了一些升级:
inline fun <T: Any, R: Any> ifLet(vararg elements: T?, closure: (List<T>) -> R): R? {
return if (elements.all { it != null }) {
closure(elements.filterNotNull())
} else null
}
这使这成为可能:
iflet("first", "sconed") {
// do somehing
} ?: run {
// do this if one of the params are null
}
基于@yole
回答的另一个想法fun <T, U, R> Pair<T?, U?>.toLet(body: (List<*>) -> R): R? {
val one = first
val two = second
if (one == null || two == null)
return null
return if (one is Pair<*, *>) {
one.toLet { a ->
body(listOf(a, listOf(two)).flatten())
}
} else {
body(listOf(one, two))
}
}
因此您可以执行以下操作
(1 to 6 to "a" to 4.5).toLet { (a, b, c, d) ->
// Rest of code
}
也许有点晚了。但现在它存在一个库来满足这一特定需求。是Konad; have look at the maybe section
我将在此处报告来自文档的示例用法:
val foo: Int? = 1
val bar: String? = "2"
val baz: Float? = 3.0f
fun useThem(x: Int, y: String, z: Float): Int = x + y.toInt() + z.toInt()
val result: Int? = ::useThem.curry()
.on(foo.maybe)
.on(bar.maybe)
.on(baz.maybe)
.nullable
// or even
val result: Result<Int> = ::useThem.curry()
.on(foo.ifNull("Foo should not be null"))
.on(bar.ifNull("Bar should not be null"))
.on(baz.ifNull("Baz should not be null"))
.result
我喜欢使用列表过滤空值的想法,当我处理相同类型时我通常会做类似的事情,但是当有多种类型时,为了避免将值解析为 Any
, 我就是这么干的
fun someFunction() {
val value1: String = this.value1 ?: return
val value2: Int = this.value2 ?: return
...
}
它有效,对我来说保持类型安全很重要
使用内置的 kotlin 功能而不添加任何额外的辅助函数来修复此问题的最直接方法如下:
fun example(first: String?, second: String?) {
first?.let { itFirst ->
second?.let { itSecond ->
// Do something just if both are != null
// itFirst & itSecond are both != null if this block is reached
}
}
}