Più variabili lasciate in Kotlin


127

C'è un modo per concatenare più let per più variabili nullable in kotlin?

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

Voglio dire, qualcosa del genere:

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

1
Vuoi N articoli, non solo 2? Tutti gli articoli necessitano dello stesso tipo o di tipi diversi? Tutti i valori devono essere passati alla funzione, come lista o come singoli parametri? Il valore restituito dovrebbe essere un singolo elemento o un gruppo dello stesso numero di elementi dell'input?
Jayson Minard,

Ho bisogno di tutti gli argomenti, possono essere due per questo caso, ma volevo anche sapere un modo per farlo di più, in Swift è così facile.
Daniel Gomez Rico,

Stai cercando qualcosa di diverso dalle risposte di seguito, in tal caso commenta qual è la differenza che stai cercando.
Jayson Minard,

Come sarebbe riferirsi al primo "esso" all'interno del secondo blocco let?
Javier Mendonça

Risposte:


48

Se interessati ecco due delle mie funzioni per risolvere questo problema.

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

Uso:


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

Questo è molto carino, ma mi manca ancora un caso in cui posso usare il primo input nel secondo. Esempio: ifLet ("A", toLower (first)) {// first = "A", second = "a"}
Otziii

Perché nell'istruzione ifLet il primo argomento non è ancora stato scartato, una funzione come la tua non è possibile. Posso suggerire di usare guardLet? È abbastanza semplice. val (first) = guardLet (100) {return} val (second) = guardLet (101) {return} val average = average (first, second) So che non è quello che hai chiesto, ma spero che aiuti.
Dario Pellegrini

Grazie. Ho diversi modi per risolvere questo problema, il motivo per cui lo dico è che in Swift è possibile avere più ifLet uno dopo l'altro separati da virgola e possono utilizzare le variabili del controllo precedente. Vorrei che questo fosse possibile anche a Kotlin. :)
Otziii

1
Potrebbe essere una risposta accettata, ma ogni chiamata è sovraccarica. Perché vm crea prima l'oggetto Funzione. Considerando anche la limitazione dex, questo aggiungerà la dichiarazione della classe Function con 2 riferimenti al metodo per ogni controllo univoco.
Oleksandr Albul

147

Ecco alcune variazioni, a seconda dello stile che si desidera utilizzare, se si dispone di tutto dello stesso tipo o di tipi diversi e se l'elenco è un numero sconosciuto di elementi ...

Tipi misti, non tutti devono essere nulli per calcolare un nuovo valore

Per i tipi misti potresti creare una serie di funzioni per ogni conteggio dei parametri che possono sembrare sciocchi, ma funzionano bene per i tipi misti:

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

Utilizzo di esempio:

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

Esegui blocco di codice quando l'elenco non ha elementi nulli

Due versioni qui, la prima per eseguire un blocco di codice quando una lista ha tutti gli elementi non nulli e la seconda per fare lo stesso quando una lista ha almeno un elemento non nullo. Entrambi i casi passano un elenco di elementi non nulli al blocco di codice:

funzioni:

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

Utilizzo di esempio:

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"

Una leggera modifica per fare in modo che la funzione riceva l'elenco degli elementi e faccia le stesse operazioni:

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

Utilizzo di esempio:

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

Queste variazioni potrebbero essere modificate per avere valori di ritorno come let().

Usa il primo elemento non nullo (Coalesce)

Simile a una funzione SQL Coalesce, restituisce il primo elemento non nullo. Due gusti della funzione:

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

Utilizzo di esempio:

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"

Altre varianti

... Ci sono altre variazioni, ma con più di una specifica questo potrebbe essere ridotto.


1
Si potrebbe anche combinare whenAllNotNullcon la destrutturazione in questo modo: listOf(a, b, c).whenAllNotNull { (d, e, f) -> println("$d $e $f").
dumptruckman

10

Puoi scrivere la tua funzione per questo:

 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

Puoi creare una arrayIfNoNullsfunzione:

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

Puoi quindi usarlo per un numero variabile di valori con let:

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

Se hai già un array puoi creare una takeIfNoNullsfunzione (ispirata a takeIfe requireNoNulls):

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

Esempio:

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

3

Per il caso di controllare solo due valori e anche di non dover lavorare con gli elenchi:

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

Esempio di utilizzo:

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

2

In realtà, puoi semplicemente farlo, sai? ;)

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

Non c'è niente di sbagliato nell'usare un normale controllo null in Kotlin.

Ed è molto più leggibile per chiunque esaminerà il tuo codice.


36
Non sarà sufficiente quando si ha a che fare con un membro di una classe mutevole.
Michał K

3
Non c'è bisogno di dare questo tipo di risposta, l'intenzione della domanda è di trovare un modo più "produttivo" di gestirlo, poiché la lingua fornisce la letscorciatoia per fare questi controlli
Alejandro Moya

1
In termini di manutenibilità, questa è la mia scelta, anche se non così elegante. Questo è chiaramente un problema che tutti incontrano continuamente e il linguaggio dovrebbe affrontare.
Brill Pappin

2

In realtà preferisco risolverlo utilizzando le seguenti funzioni di supporto:

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)

Ed ecco come dovresti usarli:

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

Ho risolto questo problema creando alcune funzioni che replicano più o meno il comportamento di with, ma accettano più parametri e invocano solo la funzione di tutti i parametri non 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) } } } } }

Quindi lo uso in questo modo:

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

Il problema ovvio con questo è che devo definire una funzione per ogni caso (numero di variabili) di cui ho bisogno, ma almeno penso che il codice sia pulito quando li uso.


1

Potresti anche farlo

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

Il compilatore si lamenterà comunque di non poter garantire che i vars non siano nulli
Peter Graham

1

Ho aggiornato un po 'la risposta prevista:

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
}

questo lo rende possibile:

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

È fantastico, ma i parametri non sono denominati e dovrebbero condividere il tipo.
Daniel Gomez Rico

0

Per qualsiasi quantità di valori da controllare puoi usare questo:

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

E verrà utilizzato in questo modo:

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

    checkNulls(dada, dede) { strings ->

    }

gli elementi inviati al blocco utilizzano il carattere jolly, devi controllare i tipi se vuoi accedere ai valori, se devi usare un solo tipo puoi mutarlo in generici

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.