多变量让在Kotlin中

Dan*_*ico 96 kotlin

有没有办法在kotlin中为多个可变变量链接多个let?

fun example(first: String?, second: String?) {
    first?.let {
        second?.let {
            // Do something just if both are != null
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的意思是,像这样:

fun example(first: String?, second: String?) {
    first?.let && second?.let { 
        // Do something just if both are != null
    }
}
Run Code Online (Sandbox Code Playgroud)

Jay*_*ard 124

以下是一些变体,具体取决于您要使用的样式,如果您拥有相同或不同类型的所有内容,以及列表未知数量的项目...

混合类型,所有都不能为null来计算新值

对于混合类型,您可以为每个参数计数构建一系列函数,这些函数可能看起来很傻,但对于混合类型可以很好地工作:

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
}
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
}
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
}
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
Run Code Online (Sandbox Code Playgroud)

用法示例:

val risk = safeLet(person.name, person.age) { name, age ->
  // do something
}   
Run Code Online (Sandbox Code Playgroud)

当list没有空项时执行代码块

这里有两种风格,第一种是当列表包含所有非空项时执行代码块,第二种是在列表至少有一个非空项时执行相同的代码.两种情况都会将非空项列表传递给代码块:

功能:

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())
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

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"
Run Code Online (Sandbox Code Playgroud)

稍微更改一下,让函数接收项目列表并执行相同的操作:

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())
    }
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

whenAllNotNull("something", "else", "matters") {
    println(it.joinToString(" "))
} // output "something else matters"
Run Code Online (Sandbox Code Playgroud)

可以将这些变化更改为具有返回值let().

使用第一个非空项(Coalesce)

与SQL Coalesce函数类似,返回第一个非null项.两种口味的功能:

fun <T: Any> coalesce(vararg options: T?): T? = options.firstOrNull { it != null }
fun <T: Any> Collection<T?>.coalesce(): T? = this.firstOrNull { it != null }
Run Code Online (Sandbox Code Playgroud)

用法示例:

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"
Run Code Online (Sandbox Code Playgroud)

其他变化

......还有其他变化,但有更多的规格,这可以缩小.


Dar*_*ini 21

If interested here are two of my functions for solving this.

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())
    }
}
Run Code Online (Sandbox Code Playgroud)

Usage:


// 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)
}
Run Code Online (Sandbox Code Playgroud)

  • 也许我错了,但看起来,例如,当您将不同类型传递给这些函数时, T 将被推导出为 any 。因此,lambda 变量第一、第二、第三都是 Any 类型,这意味着您需要将它们强制转换回来以对它们执行任何有用的操作。 (3认同)
  • 它可以被接受,但每次调用都有开销。因为vm首先创建Function对象。另外考虑到 dex 限制,这将为每个唯一检查添加带有 2 个方法引用的 Function 类声明。 (2认同)

Grz*_* D. 10

事实上,你可以简单地做到这一点,你知道吗?;)

if (first != null && second != null) {
    // your logic here...
}
Run Code Online (Sandbox Code Playgroud)

在 Kotlin 中使用普通的空检查没有任何问题。

对于每个研究你的代码的人来说,它都更具可读性。

  • 当处理可变的类成员时,这还不够。 (56认同)
  • 不需要给出这种答案,问题的目的是找到一种更“有效的方式”来处理这个问题,因为该语言提供了“let”快捷方式来进行这些检查 (4认同)
  • 就可维护性而言,这是我的选择,即使它不那么优雅。这显然是每个人都会遇到的问题,也是语言应该处理的问题。 (3认同)
  • @OkhanOkbay 可变类成员(在类主体中声明的“var”)可以通过不同的方法异步更改。即使您检查了“first”变量是否为空,在下一行中它也可能再次为空,因为那时它可能已被更改。Kotlin 编译器知道这一点,并且在下一行中它仍然是一个可为 null 的变量。在这方面使用“first?.let { it.foo() }”更安全。然而,这并不是一个日常问题,有时常规空检查的表现力很好(正如 Grzegorz D. 所说)。 (2认同)

yol*_*ole 9

你可以为此编写自己的函数:

 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
 }
Run Code Online (Sandbox Code Playgroud)

  • 如果有人要检查 4 个、另外 6 个和另外 8 个参数怎么办?!继续添加那些“花哨”的论点?!)) (4认同)

Max*_*ruz 8

我喜欢使用列表过滤空值的想法,当我使用相同的类型时,我通常会做类似的事情,但是当有多种类型时,为了避免解析为 的值Any,我只是做这样的事情

fun someFunction() {
    val value1: String = this.value1 ?: return
    val value2: Int = this.value2 ?: return
    ...
 }
Run Code Online (Sandbox Code Playgroud)

它有效,对我来说保持类型安全很重要


mfu*_*n26 7

您可以创建一个arrayIfNoNulls函数:

fun <T : Any> arrayIfNoNulls(vararg elements: T?): Array<T>? {
    if (null in elements) {
        return null
    }
    @Suppress("UNCHECKED_CAST")
    return elements as Array<T>
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以将其用于可变数量的值let

fun example(first: String?, second: String?) {
    arrayIfNoNulls(first, second)?.let { (first, second) ->
        // Do something if each element is not null
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您已经有一个数组,您可以创建一个takeIfNoNulls函数(受takeIf和启发requireNoNulls):

fun <T : Any> Array<T?>.takeIfNoNulls(): Array<T>? {
    if (null in this) {
        return null
    }
    @Suppress("UNCHECKED_CAST")
    return this as Array<T>
}
Run Code Online (Sandbox Code Playgroud)

例子:

array?.takeIfNoNulls()?.let { (first, second) ->
    // Do something if each element is not null
}
Run Code Online (Sandbox Code Playgroud)


小智 6

对于仅检查两个值并且不必使用列表的情况:

fun <T1, T2> ifNotNull(value1: T1?, value2: T2?, bothNotNull: (T1, T2) -> (Unit)) {
    if (value1 != null && value2 != null) {
        bothNotNull(value1, value2)
    }
}
Run Code Online (Sandbox Code Playgroud)

使用示例:

var firstString: String?
var secondString: String?
ifNotNull(firstString, secondString) { first, second -> Log.d(TAG, "$first, $second") }
Run Code Online (Sandbox Code Playgroud)