web-dev-qa-db-fra.com

Manière idiomatique de gérer une liste nulle ou vide dans Kotlin

Supposons que j'ai une variable activities de type List<Any>?. Si la liste n'est ni nulle ni vide, je veux faire quelque chose, sinon je veux faire autre chose. Je suis venu avec la solution suivante:

when {
    activities != null && !activities.empty -> doSomething
    else -> doSomethingElse
}

Y a-t-il un moyen plus idiomatique de faire cela à Kotlin?

23
Kirill Rakhman

Pour certaines actions simples, vous pouvez utiliser l'opérateur d'appel sécurisé, en supposant que l'action respecte également le principe de ne pas opérer sur une liste vide (pour traiter votre cas de les deux, nul et vide:

myList?.forEach { ...only iterates if not null and not empty }

Pour d'autres actions. vous pouvez écrire une fonction d'extension - deux variantes selon que vous souhaitez recevoir la liste sous la forme this ou en tant que paramètre:

inline fun <E: Any, T: Collection<E>> T?.withNotNullNorEmpty(func: T.() -> Unit): Unit {
    if (this != null && this.isNotEmpty()) {
        with (this) { func() }
    }
}

inline fun  <E: Any, T: Collection<E>> T?.whenNotNullNorEmpty(func: (T) -> Unit): Unit {
    if (this != null && this.isNotEmpty()) {
        func(this)
    }
}

Que vous pouvez utiliser comme:

fun foo() {  
    val something: List<String>? = makeListOrNot()
    something.withNotNullNorEmpty { 
        // do anything I want, list is `this`
    }

    something.whenNotNullNorEmpty { myList ->
        // do anything I want, list is `myList`
    }
}

Vous pouvez aussi faire une fonction inverse:

inline fun <E: Any, T: Collection<E>> T?.withNullOrEmpty(func: () -> Unit): Unit {
    if (this == null || this.isEmpty()) {
        func()
    }
}

J'éviterais de les chaîner car vous remplacez alors une instruction if ou when par quelque chose de plus verbeux. Et vous entrez de plus en plus dans le domaine que les solutions que je mentionne ci-dessous offrent, à savoir une branche complète pour les situations de succès/échec. 

Remarque: ces extensions ont été généralisées à tous les descendants de Collections détenant des valeurs non nulles. Et travaillez pour plus que de simples listes.

Alternatives:

La bibliothèque Result pour Kotlin vous permet de gérer facilement votre cas de "faire ceci ou cela" en fonction des valeurs de réponse. Pour Promises, vous pouvez trouver la même chose dans la bibliothèque Kovenant

Ces deux bibliothèques vous permettent de renvoyer des résultats alternatifs à partir d'une seule fonction et de créer des branches dans le code en fonction des résultats. Ils exigent que vous contrôliez le fournisseur de la "réponse" sur laquelle vous avez agi.

Ce sont de bonnes alternatives de Kotlin à Optional et Maybe.

Explorer les fonctions d'extension plus loin (et peut-être trop)

Cette section est simplement destinée à montrer que lorsque vous rencontrez un problème tel que celui que vous avez évoqué ici, vous pouvez facilement trouver de nombreuses réponses dans Kotlin afin de coder comme vous le souhaitez. Si le monde n'est pas agréable, changez le monde. Ce n'est pas une bonne ou une mauvaise réponse, mais plutôt des informations supplémentaires.

Si vous aimez les fonctions d'extension et que vous souhaitez envisager de les chaîner dans une expression, je les modifierais probablement comme suit ...

Les variantes withXyz à renvoyer this et la variable whenXyz doivent renvoyer un nouveau type permettant à la collection entière de devenir une nouvelle (peut-être même sans rapport avec l'original). Résultat dans le code suivant:

val BAD_PREFIX = "abc"
fun example(someList: List<String>?) {
    someList?.filterNot { it.startsWith(BAD_PREFIX) }
            ?.sorted()
            .withNotNullNorEmpty {
                // do something with `this` list and return itself automatically
            }
            .whenNotNullNorEmpty { list ->
                // do something to replace `list` with something new
                listOf("x","y","z")
            }
            .whenNullOrEmpty {
                // other code returning something new to replace the null or empty list
                setOf("was","null","but","not","now")
            }
}

Note: le code complet pour cette version est à la fin du post (1)

Mais vous pouvez aussi aller dans une direction complètement nouvelle avec un mécanisme "ceci sinon cela":

fun foo(someList: List<String>?) {
    someList.whenNullOrEmpty {
        // other code
    }
    .otherwise { list ->
        // do something with `list`
    }
}

Il n'y a pas de limites, soyez créatif et découvrez le pouvoir des extensions, essayez de nouvelles idées et, comme vous pouvez le constater, il existe de nombreuses variantes dans la manière dont les gens souhaitent coder ce type de situation. Stdlib ne peut pas supporter 8 variantes de ce type de méthodes sans être déroutant. Mais chaque groupe de développement peut avoir des extensions correspondant à son style de codage.

Note: le code complet pour cette version est à la fin du post (2)

Exemple de code 1:Voici le code complet pour la version "chaînée":

inline fun <E: Any, T: Collection<E>> T?.withNotNullNorEmpty(func: T.() -> Unit): T? {
    if (this != null && this.isNotEmpty()) {
        with (this) { func() }
    }
    return this
}

inline fun  <E: Any, T: Collection<E>, R: Any> T?.whenNotNullNorEmpty(func: (T) -> R?): R? {
    if (this != null && this.isNotEmpty()) {
        return func(this)
    }
    return null
}

inline fun <E: Any, T: Collection<E>> T?.withNullOrEmpty(func: () -> Unit): T? {
    if (this == null || this.isEmpty()) {
        func()
    }
    return this
}

inline fun <E: Any, T: Collection<E>, R: Any> T?.whenNullOrEmpty(func: () -> R?): R?  {
    if (this == null || this.isEmpty()) {
        return func()
    }
    return null
}

Exemple de code 2:Voici le code complet pour une bibliothèque "ceci sinon que" (avec test unitaire):

inline fun <E : Any, T : Collection<E>> T?.withNotNullNorEmpty(func: T.() -> Unit): Otherwise {
    return if (this != null && this.isNotEmpty()) {
        with (this) { func() }
        OtherwiseIgnore
    } else {
        OtherwiseInvoke
    }
}

inline fun  <E : Any, T : Collection<E>> T?.whenNotNullNorEmpty(func: (T) -> Unit): Otherwise {
    return if (this != null && this.isNotEmpty()) {
        func(this)
        OtherwiseIgnore
    } else {
        OtherwiseInvoke
    }
}

inline fun <E : Any, T : Collection<E>> T?.withNullOrEmpty(func: () -> Unit): OtherwiseWithValue<T> {
    return if (this == null || this.isEmpty()) {
        func()
        OtherwiseWithValueIgnore<T>()
    } else {
        OtherwiseWithValueInvoke(this)
    }
}

inline fun <E : Any, T : Collection<E>> T?.whenNullOrEmpty(func: () -> Unit): OtherwiseWhenValue<T> {
    return if (this == null || this.isEmpty()) {
        func()
        OtherwiseWhenValueIgnore<T>()
    } else {
        OtherwiseWhenValueInvoke(this)
    }
}

interface Otherwise {
    fun otherwise(func: () -> Unit): Unit
}

object OtherwiseInvoke : Otherwise {
    override fun otherwise(func: () -> Unit): Unit {
        func()
    }
}

object OtherwiseIgnore : Otherwise {
    override fun otherwise(func: () -> Unit): Unit {
    }
}

interface OtherwiseWithValue<T> {
    fun otherwise(func: T.() -> Unit): Unit
}

class OtherwiseWithValueInvoke<T>(val value: T) : OtherwiseWithValue<T> {
    override fun otherwise(func: T.() -> Unit): Unit {
        with (value) { func() }
    }
}

class OtherwiseWithValueIgnore<T> : OtherwiseWithValue<T> {
    override fun otherwise(func: T.() -> Unit): Unit {
    }
}

interface OtherwiseWhenValue<T> {
    fun otherwise(func: (T) -> Unit): Unit
}

class OtherwiseWhenValueInvoke<T>(val value: T) : OtherwiseWhenValue<T> {
    override fun otherwise(func: (T) -> Unit): Unit {
        func(value)
    }
}

class OtherwiseWhenValueIgnore<T> : OtherwiseWhenValue<T> {
    override fun otherwise(func: (T) -> Unit): Unit {
    }
}


class TestBrancher {
    @Test fun testOne() {
        // when NOT null or empty

        emptyList<String>().whenNotNullNorEmpty { list ->
            fail("should not branch here")
        }.otherwise {
            // sucess
        }

        nullList<String>().whenNotNullNorEmpty { list ->
            fail("should not branch here")
        }.otherwise {
            // sucess
        }

        listOf("a", "b").whenNotNullNorEmpty { list ->
            assertEquals(listOf("a", "b"), list)
        }.otherwise {
            fail("should not branch here")
        }

        // when YES null or empty

        emptyList<String>().whenNullOrEmpty {
            // sucess
        }.otherwise { list ->
            fail("should not branch here")
        }

        nullList<String>().whenNullOrEmpty {
            // success
        }.otherwise {
            fail("should not branch here")
        }

        listOf("a", "b").whenNullOrEmpty {
            fail("should not branch here")
        }.otherwise { list ->
            assertEquals(listOf("a", "b"), list)
        }

        // with NOT null or empty

        emptyList<String>().withNotNullNorEmpty {
            fail("should not branch here")
        }.otherwise {
            // sucess
        }

        nullList<String>().withNotNullNorEmpty {
            fail("should not branch here")
        }.otherwise {
            // sucess
        }

        listOf("a", "b").withNotNullNorEmpty {
            assertEquals(listOf("a", "b"), this)
        }.otherwise {
            fail("should not branch here")
        }

        // with YES null or empty

        emptyList<String>().withNullOrEmpty {
            // sucess
        }.otherwise {
            fail("should not branch here")
        }

        nullList<String>().withNullOrEmpty {
            // success
        }.otherwise {
            fail("should not branch here")
        }

        listOf("a", "b").withNullOrEmpty {
            fail("should not branch here")
        }.otherwise {
            assertEquals(listOf("a", "b"), this)
        }


    }

    fun <T : Any> nullList(): List<T>? = null
}
27
Jayson Minard

METTRE À JOUR:

kotlin 1.3 fournit isNullOrEmpty maintenant!

https://Twitter.com/kotlin/status/1050426794682306562


essaye ça! très clair.

var array: List<String>? = null
if (array.orEmpty().isEmpty()) {
    // empty
} else {
    // not empty
}
25
tiiime

Outre les autres réponses, vous pouvez également utiliser l'opérateur d'appel sécurisé en combinaison avec la méthode d'extension isNotEmpty(). En raison de l'appel sécurisé, la valeur de retour est en fait Boolean? qui peut être soit true, false ou null. Pour utiliser l'expression dans une clause if ou when, vous devez vérifier explicitement si c'est true:

when {
    activities?.isNotEmpty() == true -> doSomething
    else -> doSomethingElse
}

Syntaxe alternative utilisant l'opérateur elvis:

when {
    activities?.isNotEmpty() ?: false -> doSomething
    else -> doSomethingElse
}
5
Kirill Rakhman

Plus simple serait,

if(activities?.isNotEmpty() == true) doSomething() else doSomethingElse()
5
Shivanand Darur

Pensez à utiliser ?.forEach si nécessaire

activities?.forEach {
  doSmth(it)
}

Si vous voulez exactement le comportement que vous avez décrit, votre variante se lit mieux que toute autre chose plus concise que je puisse imaginer (Pourtant, une simple if devrait suffire)

2
defhlt

Dans mon cas, price est une option. Je traite le cas de la manière suivante avec orEmpty() qui renvoie le tableau donné ou un tableau vide si le tableau donné est null.

val safeArray  = poi.prices.orEmpty()
if (!safeArray.isEmpty()) {
   ...
}
0
Ilker Baltaci