Laisser plusieurs variables dans Kotlin


127

Existe-t-il un moyen de chaîner plusieurs let pour plusieurs variables nullables dans kotlin?

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

Je veux dire, quelque chose comme ça:

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

1
Voulez-vous N articles, pas seulement 2? Tous les éléments ont-ils besoin du même type ou de types différents? Toutes les valeurs doivent-elles être transmises à la fonction, sous forme de liste ou de paramètres individuels? La valeur de retour doit-elle être un élément unique ou un groupe du même nombre d'éléments que l'entrée?
Jayson Minard

J'ai besoin de tous les arguments, peut être deux pour ce cas, mais je voulais aussi savoir un moyen de le faire pour plus, en swift est si facile.
Daniel Gomez Rico le

Cherchez-vous quelque chose de différent des réponses ci-dessous, si oui, commentez quelle est la différence que vous recherchez.
Jayson Minard

Comment serait-ce de faire référence au premier "il" dans le second bloc let?
Javier Mendonça

Réponses:


48

Si vous êtes intéressé, voici deux de mes fonctions pour résoudre ce problème.

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

Usage:


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

C'est très bien, mais il me manque toujours un cas où je peux utiliser la première entrée dans la seconde. Exemple: ifLet ("A", toLower (first)) {// first = "A", second = "a"}
Otziii

Parce que dans l'instruction ifLet, le premier argument n'a pas encore été déballé, une fonction comme la vôtre n'est pas possible. Puis-je suggérer d'utiliser guardLet? C'est assez simple. val (first) = guardLet (100) {return} val (second) = guardLet (101) {return} val average = average (first, second) Je sais que ce n'est pas ce que vous avez demandé, mais j'espère que cela vous aidera.
Dario Pellegrini

Merci. J'ai plusieurs façons de résoudre ce problème, la raison en est que dans Swift, il est possible d'avoir plusieurs ifLets les uns après les autres séparés par des virgules et ils peuvent utiliser les variables de la vérification précédente. J'aurais aimé que cela soit également possible à Kotlin. :)
Otziii

1
Cela pourrait être une réponse acceptée, mais il y a des frais généraux pour chaque appel. Parce que vm crée d'abord l'objet Function. Compte tenu également de la limitation de dex, cela ajoutera une déclaration de classe Function avec 2 références de méthode pour chaque vérification unique.
Oleksandr Albul

147

Voici quelques variantes, selon le style que vous voudrez utiliser, si vous avez tout de types identiques ou différents, et si la liste d'un nombre inconnu d'éléments ...

Types mixtes, tous ne doivent pas être nuls pour calculer une nouvelle valeur

Pour les types mixtes, vous pouvez créer une série de fonctions pour chaque nombre de paramètres qui peuvent sembler ridicules, mais qui fonctionnent bien pour les types mixtes:

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

Exemple d'utilisation:

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

Exécuter un bloc de code lorsque la liste ne contient aucun élément nul

Deux saveurs ici, d'abord pour exécuter un bloc de code lorsqu'une liste contient tous les éléments non nuls, et deuxièmement pour faire de même lorsqu'une liste a au moins un élément non nul. Les deux cas passent une liste d'éléments non nuls au bloc de code:

Les fonctions:

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

Exemple d'utilisation:

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"

Un léger changement pour que la fonction reçoive la liste des éléments et effectue les mêmes opérations:

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

Exemple d'utilisation:

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

Ces variations peuvent être modifiées pour avoir des valeurs de retour telles que let().

Utiliser le premier élément non nul (Coalesce)

Similaire à une fonction SQL Coalesce, renvoie le premier élément non nul. Deux saveurs de la fonction:

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

Exemple d'utilisation:

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"

Autres variations

... Il existe d'autres variantes, mais avec plus de spécifications, cela pourrait être réduit.


1
Vous pouvez également combiner whenAllNotNullavec déstructurant comme ceci: listOf(a, b, c).whenAllNotNull { (d, e, f) -> println("$d $e $f").
dumptruckman

10

Vous pouvez écrire votre propre fonction pour cela:

 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

Vous pouvez créer une arrayIfNoNullsfonction:

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

Vous pouvez ensuite l'utiliser pour un nombre variable de valeurs avec let:

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

Si vous avez déjà un tableau, vous pouvez créer une takeIfNoNullsfonction (inspirée de takeIfet requireNoNulls):

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

Exemple:

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

3

Pour le cas où il suffit de vérifier deux valeurs et de ne pas avoir à travailler avec des listes:

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

Exemple d'utilisation:

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

2

En fait, vous pouvez simplement le faire, vous savez? ;)

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

Il n'y a rien de mal à utiliser un contrôle nul normal dans Kotlin.

Et c'est beaucoup plus lisible pour tous ceux qui examineront votre code.


36
Ce ne sera pas suffisant lorsqu'il s'agit d'un membre de classe mutable.
Michał K

3
Inutile de donner ce genre de réponse, l'intention de la question est de trouver un moyen plus "productif" de gérer cela, puisque le langage fournit le letraccourci pour faire ces vérifications
Alejandro Moya

1
En termes de maintenabilité, c'est mon choix, même si ce n'est pas aussi élégant. C'est clairement un problème que tout le monde rencontre tout le temps, et la langue devrait traiter.
Brill Pappin le

2

Je préfère en fait le résoudre en utilisant les fonctions d'assistance suivantes:

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)

Et voici comment vous devez les utiliser:

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

J'ai résolu ce problème en créant des fonctions qui répliquent plus ou moins le comportement de with, mais prennent plusieurs paramètres et n'appellent que la fonction de tous les paramètres est non nulle.

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

Ensuite, je l'utilise comme ceci:

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

Le problème évident avec ceci est que je dois définir une fonction pour chaque cas (nombre de variables) dont j'ai besoin, mais au moins je pense que le code semble propre lors de leur utilisation.


1

Tu pourrais aussi faire ça

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

Le compilateur se plaindra toujours de ne pas pouvoir garantir que les vars ne sont pas nulles
Peter Graham

1

J'ai légèrement amélioré la réponse attendue:

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
}

cela rend cela possible:

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

C'est cool, mais les paramètres ne sont pas nommés et devraient partager le type.
Daniel Gomez Rico le

0

Pour toute quantité de valeurs à vérifier, vous pouvez utiliser ceci:

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

Et il sera utilisé comme ceci:

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

    checkNulls(dada, dede) { strings ->

    }

les éléments envoyés au bloc utilisent le caractère générique, vous devez vérifier les types si vous souhaitez accéder aux valeurs, si vous avez besoin d'utiliser un seul type, vous pouvez le transformer en génériques

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.