Kotlinの複数の変数let


127

kotlinで複数のnull許容変数の複数の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
2つだけでなく、N個のアイテムが必要ですか?すべてのアイテムに同じタイプまたは異なるタイプが必要ですか?すべての値を関数として、リストとして、または個別のパラメーターとして渡す必要がありますか?戻り値は、単一のアイテムであるか、入力と同じ数のアイテムのグループである必要がありますか?
ジェイソンミナード2016

私はすべての引数が必要ですが、この場合は2つにすることができますが、これをさらに行う方法を知りたいと思ったのですが、迅速に行うのはとても簡単です。
Daniel Gomez Rico

以下の答えとは異なるものを探していますか?そうであれば、求めている違いをコメントしてください。
ジェイソンミナード2016

2番目のletブロック内の最初の「it」をどのように参照するのでしょうか。
ハビエルメンドンサ、

回答:


48

ここで興味がある場合は、これを解決するための2つの機能があります。

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

これは非常に便利ですが、2番目の入力で最初の入力を使用できるケースがまだありません。例:ifLet( "A"、toLower(first)){// first = "A"、second = "a"}
Otziii

ifLetステートメントの原因は、最初の引数がまだラップされていないため、あなたのような関数は不可能です。guardLetの使用を提案できますか?それはかなり簡単です。val(first)= guardLet(100){return} val(second)= guardLet(101){return} val average = average(first、second)これはあなたの質問ではないことはわかっていますが、お役に立てば幸いです。
ダリオペレグリーニ

ありがとう。私はこれを解決する方法が複数ありますが、Swiftではコンマで区切られた後にifLetsを複数持つことが可能で、前のチェックの変数を使用できるためです。これがKotlinでも可能だったといいのですが。:)
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項目がない場合にコードブロックを実行する

ここでは2つのフレーバーがあり、1つはリストにnull以外の項目がすべて含まれている場合にコードのブロックを実行し、2つ目はリストに少なくとも1つのnull以外の項目がある場合に同じことを実行します。どちらの場合も、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()

最初のnull以外のアイテムを使用します(結合)

SQL Coalesce関数と同様に、最初のnull以外の項目を返します。関数の2つのフレーバー:

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

2つの値をチェックするだけで、リストを操作する必要がない場合:

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で通常のnullチェックを使用しても問題はありません。

また、コードを調べるすべての人にとって、はるかに読みやすくなります。


36
変更可能なクラスメンバーを処理する場合は十分ではありません。
のMichałK

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

あなたもこれを行うことができます

if (listOfNotNull(var1, var2, var3).size == 3) {
        // All variables are non-null
}

コンパイラーは依然として、変数がnullでないことを保証できないと不平を言うでしょう
Peter Graham

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
}

これはすばらしいことですが、パラメーターには名前が付けられておらず、タイプを共有する必要があります。
ダニエルゴメスリコ

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

    }

ブロックに送信される要素はワイルドカードを使用しています。値にアクセスする場合はタイプを確認する必要があります。1つのタイプのみを使用する必要がある場合は、これをジェネリックスに変更できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.