Beberapa variabel biarkan masuk Kotlin

127

Apakah ada cara untuk merangkai beberapa memungkinkan untuk beberapa variabel nullable di kotlin?

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

Maksud saya, sesuatu seperti ini:

fun example(first: String?, second: String?) {
    first?.let && second?.let { 
        // Do something just if both are != null
    }
}
Daniel Gomez Rico
sumber
1
Apakah Anda ingin N item, bukan hanya 2? Apakah semua barang membutuhkan jenis yang sama, atau jenis yang berbeda? Haruskah semua nilai diteruskan ke dalam fungsi, sebagai daftar, atau sebagai parameter individual? Haruskah nilai yang dikembalikan menjadi satu item atau sekelompok item yang sama sebagai input?
Jayson Minard
Saya perlu semua argumen, bisa menjadi dua untuk kasus ini tetapi juga ingin tahu cara untuk melakukan ini lebih banyak, dengan cepat sangat mudah.
Daniel Gomez Rico
Apakah Anda mencari sesuatu yang berbeda dari jawaban di bawah ini, jika demikian beri komentar apa perbedaan yang Anda cari.
Jayson Minard
Bagaimana rasanya merujuk pada "itu" pertama dalam blok let kedua?
Javier Mendonça

Jawaban:

48

Jika tertarik, berikut adalah dua fungsi saya untuk menyelesaikan ini.

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

Pemakaian:


// 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)
}
Dario Pellegrini
sumber
Ini sangat bagus, tetapi saya masih kehilangan kasus di mana saya dapat menggunakan masukan pertama di masukan kedua. Contoh: ifLet ("A", toLower (first)) {// first = "A", second = "a"}
Otziii
Penyebab dalam pernyataan ifLet argumen pertama belum dibuka, fungsi seperti milik Anda tidak memungkinkan. Bisakah saya menyarankan untuk menggunakan guardLet? Ini sangat mudah. val (pertama) = guardLet (100) {return} val (kedua) = guardLet (101) {return} val rata-rata = rata-rata (pertama, kedua) Saya tahu bukan itu yang Anda tanyakan, tapi semoga membantu.
Dario Pellegrini
Terima kasih. Saya memiliki banyak cara untuk menyelesaikan ini, alasan untuk mengatakannya adalah bahwa di Swift dimungkinkan untuk memiliki beberapa ifLets setelah satu sama lain dipisahkan dengan koma dan mereka dapat menggunakan variabel dari pemeriksaan sebelumnya. Saya berharap ini juga bisa dilakukan di Kotlin. :)
Otziii
1
Ini bisa jadi jawaban yang diterima, tetapi ada overhead pada setiap panggilan. Karena vm membuat objek Function terlebih dahulu. Juga mempertimbangkan batasan dex, ini akan menambahkan deklarasi kelas Fungsi dengan 2 referensi metode untuk setiap pemeriksaan unik.
Oleksandr Albul
147

Berikut adalah beberapa variasi, tergantung pada gaya apa yang ingin Anda gunakan, jika Anda memiliki semua jenis yang sama atau berbeda, dan jika daftar jumlah item tidak diketahui ...

Jenis campuran, semua tidak boleh null untuk menghitung nilai baru

Untuk tipe campuran, Anda dapat membuat serangkaian fungsi untuk setiap jumlah parameter yang mungkin terlihat konyol, tetapi berfungsi dengan baik untuk tipe campuran:

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

Contoh penggunaan:

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

Jalankan blok kode ketika daftar tidak memiliki item nol

Dua ragam di sini, pertama untuk mengeksekusi blok kode ketika daftar memiliki semua item bukan nol, dan kedua untuk melakukan hal yang sama ketika daftar memiliki setidaknya satu item bukan nol. Kedua kasus meneruskan daftar item bukan null ke blok kode:

Fungsi:

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

Contoh penggunaan:

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"

Sedikit perubahan agar fungsi menerima daftar item dan melakukan operasi yang sama:

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

Contoh penggunaan:

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

Variasi ini dapat diubah agar memiliki nilai pengembalian seperti let().

Gunakan item non-null pertama (Coalesce)

Mirip dengan fungsi SQL Coalesce, kembalikan item bukan null pertama. Dua jenis fungsi:

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

Contoh penggunaan:

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"

Variasi lainnya

... Ada variasi lain, tetapi dengan lebih banyak spesifikasi, ini dapat dipersempit.

Jayson Minard
sumber
1
Anda juga dapat menggabungkan whenAllNotNulldengan destrukturisasi seperti: listOf(a, b, c).whenAllNotNull { (d, e, f) -> println("$d $e $f").
dumptruckman
10

Anda dapat menulis fungsi Anda sendiri untuk itu:

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

Anda dapat membuat arrayIfNoNullsfungsi:

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

Anda kemudian dapat menggunakannya untuk sejumlah nilai variabel dengan let:

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

Jika Anda sudah memiliki array, Anda dapat membuat takeIfNoNullsfungsi (terinspirasi oleh takeIfdan requireNoNulls):

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

Contoh:

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

Untuk kasus hanya memeriksa dua nilai dan juga tidak harus bekerja dengan daftar:

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

Contoh penggunaan:

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

Sebenarnya, Anda bisa melakukan ini begitu saja, Anda tahu? ;)

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

Tidak ada yang salah dalam menggunakan pemeriksaan null normal di Kotlin.

Dan itu jauh lebih mudah dibaca oleh semua orang yang akan melihat kode Anda.

Grzegorz D.
sumber
36
Itu tidak akan cukup ketika berhadapan dengan anggota kelas yang bisa berubah.
Michał K
3
Tidak perlu memberikan jawaban seperti ini, maksud dari pertanyaan ini adalah untuk menemukan cara yang lebih "produktif" untuk menangani hal ini, karena bahasa menyediakan jalan letpintas untuk melakukan pemeriksaan ini
Alejandro Moya
1
Dalam hal perawatan, ini adalah pilihan saya, meskipun tidak se-elegan. Ini jelas merupakan masalah yang dihadapi setiap orang sepanjang waktu, dan bahasanya harus ditangani.
Brill Pappin
2

Saya sebenarnya lebih suka menyelesaikannya menggunakan fungsi helper berikut:

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)

Dan inilah cara Anda menggunakannya:

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
  .
  .
  .
}
Moshe Bixenshpaner
sumber
1

Saya menyelesaikan ini dengan membuat beberapa fungsi yang kurang lebih mereplikasi perilaku with, tetapi mengambil beberapa parameter dan hanya memanggil fungsi semua parameter yang bukan 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) } } } } }

Kemudian saya menggunakannya seperti ini:

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

Masalah yang jelas dengan ini adalah bahwa saya harus menentukan fungsi untuk setiap kasus (jumlah variabel) yang saya butuhkan, tetapi setidaknya saya pikir kodenya terlihat bersih saat menggunakannya.

Jon
sumber
1

Anda juga bisa melakukan ini

if (listOfNotNull(var1, var2, var3).size == 3) {
        // All variables are non-null
}
Amy Eubanks
sumber
Kompilator masih akan mengeluh bahwa tidak dapat menjamin bahwa vars tidak null
Peter Graham
1

Saya telah meningkatkan sedikit jawaban yang diharapkan:

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
}

ini memungkinkan ini:

iflet("first", "sconed") {
    // do somehing
} ?: run {
    // do this if one of the params are null
}
yohai knaani
sumber
Itu keren, tetapi parameternya tidak dinamai dan harus berbagi tipenya.
Daniel Gomez Rico
0

Untuk sejumlah nilai yang akan diperiksa, Anda dapat menggunakan ini:

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

Dan itu akan digunakan seperti ini:

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

    checkNulls(dada, dede) { strings ->

    }

elemen yang dikirim ke blok menggunakan wildcard, Anda perlu memeriksa jenis jika Anda ingin mengakses nilai, jika Anda hanya perlu menggunakan satu jenis, Anda dapat mengubah ini menjadi generik

Alejandro Moya
sumber