Kotlin中的多变量let


127

有没有办法在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
    }
}

1
是否要N个项目,而不仅仅是2个?所有项目都需要相同类型还是不同类型?是否应将所有值作为列表或作为单个参数传递给函数?返回值是输入的单个项目还是一组相同数量的项目?
杰森·米纳德

我需要所有参数,在这种情况下可以是两个,但也想知道一种实现此目的的方法,迅速是很容易的。
丹尼尔·戈麦斯·里科

您是否正在寻找与以下答案不同的东西,如果有,请评论您要寻找的区别是什么。
杰森·米纳德'02

在第二个let块中如何引用第一个“ it”呢?
哈维尔·门

Answers:


48

如果有兴趣,这里是我解决此问题的两个功能。

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)
}

这非常好,但是我仍然缺少在第二个输入中可以使用第一个输入的情况。示例:ifLet(“ A”,toLower(first)){//第一=“ A”,第二=“ a”}
Otziii,

由于ifLet语句中的第一个参数尚未解包,因此无法实现像your这样的功能。我可以建议使用guardLet吗?非常简单。val(first)= guardLet(100){return} val(second)= guardLet(101){return} val average = average(first,second)我知道这不是您要的内容,但希望对您有所帮助。
达里奥·佩莱格里尼

谢谢。我有多种方法可以解决这个问题,原因是在Swift中,可能有多个ifLets,它们之间用逗号隔开,并且可以使用上一个检查的变量。我希望在科特林也有可能。:)
Otziii

1
可以接受答案,但每次通话都有开销。因为vm首先创建Function对象。同样考虑到dex限制,这将为每个唯一检查添加带有2个方法引用的Function类声明。
Oleksandr Albul

146

以下是一些变体,具体取决于您要使用的样式,是否具有相同或不同的类型,以及是否列出了未知数量的项目...

混合类型,所有类型都不能为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
}   

当列表中没有空项目时执行代码块

这里有两种形式,一种是在列表具有所有非空项目时执行代码块,第二种是在列表具有至少一个非空项目时执行相同的代码。两种情况都将一系列非null的项目传递给代码块:

功能:

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"

这些变体可以更改为具有的返回值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 }

用法示例:

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"

其他变化

...还有其他变体,但是如果有更多规范,则可以缩小范围。


1
您还可以结合whenAllNotNull与解构像这样:listOf(a, b, c).whenAllNotNull { (d, e, f) -> println("$d $e $f")
dumptruckman

10

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

 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
 }

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>
}

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

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

如果您已有数组,则可以创建一个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>
}

例:

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

3

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

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") }

2

其实,您可以简单地做到这一点,知道吗?;)

if (first != null && second != null) {
    // your logic here...
}

在Kotlin中使用常规的空检查没有什么错。

而且,对要研究您的代码的每个人来说,它的可读性都更高。


36
与一个可变的类成员打交道还不够。
米哈尔ķ

3
无需给出这种答案,问题的目的是找到一种更“富有成效的方式”来处理此问题,因为该语言提供了let进行这些检查的捷径
Alejandro Moya

1
在可维护性方面,这是我的选择,即使没有那么优雅。显然,这是每个人都经常遇到的问题,这种语言应该处理。
Brill Pappin

2

我实际上更喜欢使用以下辅助函数来解决它:

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
  .
  .
  .
}

1

我通过创建一些函数来解决此问题,这些函数或多或少地复制了with的行为,但是采用了多个参数,并且仅调用所有参数的函数为非null。

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")

明显的问题是,我必须为每种情况(变量数)定义一个函数,但是至少我认为使用它们时代码看起来很干净。



1

我已经将预期答案做了一些升级:

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
}

那很酷,但是参数没有命名,应该共享类型。
Daniel Gomez Rico

0

对于要检查的任何数量的值,您可以使用以下命令:

    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 ->

    }

发送给块的元素使用通配符,如果要访问值,则需要检查类型,如果只需要使用一种类型,则可以将其更改为泛型

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.