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?
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
}
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
}
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
}
Plus simple serait,
if(activities?.isNotEmpty() == true) doSomething() else doSomethingElse()
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)
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()) {
...
}