Çoklu değişken Kotlin'e girme


127

Kotlin'de birden çok boş değer atanabilir değişken için birden çok izin zincirlemenin herhangi bir yolu var mı?

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

Demek istediğim, bunun gibi bir şey:

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

1
Yalnızca 2 değil, N öğe mi istiyorsunuz? Tüm öğelerin aynı türe mi yoksa farklı türlere mi ihtiyacı var? Tüm değerler işleve liste olarak mı yoksa ayrı parametreler olarak mı aktarılmalı? Dönüş değeri tek bir öğe mi yoksa girdi olarak aynı sayıda öğeden oluşan bir grup mu olmalıdır?
Jayson Minard

Tüm argümanlara ihtiyacım var, bu dava için iki olabilir ama aynı zamanda bunu daha fazlası için yapmanın bir yolunu bilmek istedim, hızlı bir şekilde çok kolay.
Daniel Gomez Rico

Aşağıdaki cevaplardan farklı bir şey mi arıyorsunuz, öyleyse aradığınız farkın ne olduğunu yorumlayın.
Jayson Minard

İkinci let bloğundaki ilk "ona" atıfta bulunmak nasıl olur?
Javier Mendonça

Yanıtlar:


48

Burada ilgileniyorsanız, bunu çözmek için benim işlevlerimden ikisi.

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

Kullanımı:


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

Bu çok güzel, ancak yine de ikinci girdideki ilk girdiyi kullanabileceğim bir durumu kaçırıyorum. Örnek: ifLet ("A", toLower (birinci)) {// birinci = "A", ikinci = "a"}
Otziii

İfLet ifadesindeki ilk argüman henüz açılmamış olduğundan, sizin gibi bir işlev mümkün değildir. GuardLet kullanmayı önerebilir miyim? Oldukça basit. val (birinci) = guardLet (100) {dönüş} val (ikinci) = guardLet (101) {return} val ortalama = ortalama (birinci, ikinci) Sorduğunuz şeyin bu olmadığını biliyorum ama yardımcı olacağını umuyorum.
Dario Pellegrini

Teşekkürler. Bunu çözmenin birden çok yolu var, söylememin nedeni Swift'de virgülle ayrılmış birbirlerinden sonra birden fazla ifLet'e sahip olmanın mümkün olması ve önceki kontrolün değişkenlerini kullanabilmeleridir. Bunun Kotlin'de de mümkün olmasını dilerdim. :)
Otziii

1
Cevap kabul edilebilir, ancak her aramada ek yük vardır. Çünkü vm, öncelikle Function nesnesini oluşturur. Ayrıca dex sınırlaması dikkate alındığında, bu, her benzersiz kontrol için 2 yöntem referanslı Function sınıfı bildirimi ekleyecektir.
Oleksandr Albul

147

İşte hangi stili kullanmak istediğinize bağlı olarak, aynı veya farklı türde her şeye sahipseniz ve liste bilinmeyen sayıda öğe varsa ...

Karışık türler, yeni bir değer hesaplamak için tümü boş olmamalıdır

Karma türler için, her parametre sayısı için aptalca görünebilecek, ancak karma türler için iyi çalışan bir dizi işlev oluşturabilirsiniz:

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

Örnek kullanım:

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

Listede boş öğe olmadığında kod bloğunu çalıştır

Buradaki iki çeşit, ilk olarak bir liste boş olmayan tüm öğelere sahip olduğunda kod bloğunu yürütmek ve ikincisi, bir liste en az bir boş olmayan öğe içerdiğinde aynı şeyi yapmaktır. Her iki durum da kod bloğuna boş olmayan öğelerin bir listesini iletir:

Fonksiyonlar:

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

Örnek kullanım:

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"

İşlevin öğe listesini alması ve aynı işlemleri yapması için küçük bir değişiklik:

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

Örnek kullanım:

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

Bu varyasyonlar, gibi dönüş değerlerine sahip olacak şekilde değiştirilebilir let().

İlk boş olmayan öğeyi kullanın (Coalesce)

SQL Coalesce işlevine benzer şekilde, boş olmayan ilk öğeyi döndürür. Fonksiyonun iki çeşidi:

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

Örnek kullanım:

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"

Diğer varyasyonlar

... Başka varyasyonlar da var, ancak daha fazla spesifikasyonla bu daraltılabilir.


1
Ayrıca birleştirmek olabilir whenAllNotNullşöyle kurucuların ile: listOf(a, b, c).whenAllNotNull { (d, e, f) -> println("$d $e $f").
dumptruckman

10

Bunun için kendi fonksiyonunuzu yazabilirsiniz:

 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

Bir arrayIfNoNullsişlev oluşturabilirsiniz :

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

Daha sonra aşağıdakilerle değişken sayıda değer için kullanabilirsiniz let:

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

Zaten bir diziniz varsa bir takeIfNoNullsişlev oluşturabilirsiniz ( takeIfve esinlenerek requireNoNulls):

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

Misal:

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

3

Sadece iki değeri kontrol etmek ve ayrıca listelerle çalışmak zorunda kalmamak için:

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

Kullanım örneği:

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

2

Aslında bunu basitçe yapabilirsiniz, biliyor musunuz? ;)

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

Kotlin'de normal bir sıfır kontrolü kullanmanın yanlış bir tarafı yoktur.

Ve kodunuza bakacak herkes için çok daha okunabilir.


36
Değişken bir sınıf üyesiyle uğraşırken yeterli olmayacak.
Michał K

3
Bu tür bir cevap vermeye gerek yok, sorunun amacı, bunu halletmenin daha "verimli bir yolunu" bulmaktır, çünkü dil, letbu kontrolleri yapmak için kısayol sağlar
Alejandro Moya

1
Sürdürülebilirlik açısından, bu kadar zarif olmasa da bu benim seçimim. Bu açıkça herkesin her zaman karşılaştığı bir sorundur ve dilin ilgilenmesi gerekir.
Brill Pappin

2

Aslında aşağıdaki yardımcı işlevleri kullanarak çözmeyi tercih ediyorum:

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)

Ve işte bunları nasıl kullanmanız gerektiği:

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

Bunu, davranışını az çok yineleyen, ancak birden çok parametre alan ve yalnızca tüm parametrelerin işlevini boş olmayan bir işlev olarak çağıran bazı işlevler oluşturarak çözdüm.

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

O zaman bunu şöyle kullanıyorum:

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

Bununla ilgili bariz sorun, ihtiyacım olan her durum (değişken sayısı) için bir işlev tanımlamam gerekmesidir, ancak en azından kodun bunları kullanırken temiz göründüğünü düşünüyorum.


1

Bunu da yapabilirsin

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

Derleyici yine de değişkenlerin boş olmadığını garanti edemeyeceğinden şikayet edecek
Peter Graham

1

Beklenen yanıtı biraz yükselttim:

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
}

bu, bunu mümkün kılar:

iflet("first", "sconed") {
    // do somehing
} ?: run {
    // do this if one of the params are null
}

Bu harika, ancak parametreler adlandırılmamış ve türü paylaşmalıdır.
Daniel Gomez Rico

0

Herhangi bir miktarda değerin kontrol edilmesi için şunu kullanabilirsiniz:

    fun checkNulls(vararg elements: Any?, block: (Array<*>) -> Unit) {
        elements.forEach { if (it == null) return }
        block(elements.requireNoNulls())
    }

Ve şu şekilde kullanılacak:

    val dada: String? = null
    val dede = "1"

    checkNulls(dada, dede) { strings ->

    }

bloğa gönderilen öğeler joker karakter kullanıyor, değerlere erişmek istiyorsanız türleri kontrol etmeniz gerekiyor, tek bir tür kullanmanız gerekiyorsa, bunu jenerik olarak değiştirebilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.