Multiple variable let in Kotlin

2019-01-13 14:16发布

问题:

Is there any way to chain multiple lets for multiple nullable variables in kotlin?

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

I mean, something like this:

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

回答1:

Here are a few variations, depending on what style you will want to use, if you have everything of same or different types, and if the list unknown number of items...

Mixed types, all must not be null to calculate a new value

For mixed types you could build a series of functions for each parameter count that may look silly, but work nicely for mixed types:

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

Example usage:

val risk = safeLet(person.name, person.age) { name, age ->
  // do something
}   

Execute block of code when list has no null items

Two flavours here, first to execute block of code when a list has all non null items, and second to do the same when a list has at least one not null item. Both cases pass a list of non null items to the block of code:

Functions:

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

Example usage:

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"

A slight change to have the function receive the list of items and do the same operations:

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

Example usage:

whenAllNotNull("something", "else", "matters") {
    println(it.joinToString(" "))
} // output "something else matters"

These variations could be changed to have return values like let().

Use the first non-null item (Coalesce)

Similar to a SQL Coalesce function, return the first non null item. Two flavours of the function:

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

Example usage:

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"

Other variations

...There are other variations, but with more of a specification this could be narrowed down.



回答2:

You can write your own function for that:

 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
 }


回答3:

In the spirit of Kotlin's various *NotNull and *OrNull functions you can create an arrayOfNotNullOrNull function to allow you to create an array from "multiple nullable variables" if all the variables are not null and null otherwise:

fun <T : Any> arrayOfNotNullOrNull(vararg elements: T?): Array<T>? {
    for (element in elements) {
        if (element == null) {
            return null
        }
    }
    return elements as Array<T>
}

You can then use it for a variable number of values with let:

fun example(first: String?, second: String?) {
    arrayOfNotNullOrNull(first, second)?.let {
        // Do something just if both are != null
        // e.g. val (notNullFirst, notNullSecond) = it ...
    }
}

If you already have your nullable values in a collection then you could create a noNullsOrNull extension function similar to kotlin.collections.requireNoNulls but that returns null instead of throwing an exception.



回答4:

For the case of just checking two values and also not having to work with lists:

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

Usage example:

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


回答5:

Actually, you can simply do this, you know? ;)

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

There's nothing wrong in using a normal null-check in Kotlin.

And it's far more readable for everyone who will look into your code.



标签: kotlin