when - Manera idiomática de manejar nullable o lista vacía en Kotlin
when function kotlin (7)
Digamos que tengo una variable de activities
de tipo List<Any>?
. Si la lista no es nula y no está vacía, quiero hacer algo, de lo contrario quiero hacer otra cosa. Se me ocurrió la siguiente solución:
when {
activities != null && !activities.empty -> doSomething
else -> doSomethingElse
}
¿Hay alguna forma más idiomática de hacer esto en Kotlin?
Además de las otras respuestas, también puede utilizar el operador de llamada segura en combinación con el método de extensión isNotEmpty()
. Debido a la llamada segura, el valor de retorno es en realidad Boolean?
que puede ser true
, false
o null
. Para usar la expresión en una cláusula if
o when
, deberá verificar explícitamente si es true
:
when {
activities?.isNotEmpty() == true -> doSomething
else -> doSomethingElse
}
Sintaxis alternativa utilizando el operador elvis:
when {
activities?.isNotEmpty() ?: false -> doSomething
else -> doSomethingElse
}
Considere usar ?.forEach
si es apropiado
activities?.forEach {
doSmth(it)
}
Si desea exactamente el comportamiento que describió, creo que su variante se lee mejor que cualquier otra cosa más concisa que pueda imaginar. (Sin embargo, simple if
suficiente)
En mi caso, los precios son opcionales. orEmpty()
el caso de la siguiente manera con orEmpty()
que devuelve la matriz dada o una matriz vacía si la matriz dada es nula.
val safeArray = poi.prices.orEmpty()
if (!safeArray.isEmpty()) {
...
}
En primer lugar, quería recomendar una función de extensión además de la respuesta de @ mlatu, que controla la condición de else
public inline fun Map.forEachElse(operation: (Map.Entry) -> Unit, elseBlock: () -> Unit): Unit {
if (!empty)
for (element in this) operation(element)
else
elseBlock()
}
Pero el uso no es tan hermoso.
En realidad estás buscando una tal vez monada.
La forma más sencilla sería,
if(activities?.isNotEmpty() == true) doSomething() else doSomethingElse()
Para algunas acciones simples puede usar el operador de llamada segura, asumiendo que la acción también respeta no operar en una lista vacía (para manejar su caso de nulo y vacío:
myList?.forEach { ...only iterates if not null and not empty }
Para otras acciones. puede escribir una función de extensión - dos variaciones dependiendo de si desea recibir la lista como this
o como parámetro:
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 puedes usar como:
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`
}
}
También puedes hacer función inversa:
inline fun <E: Any, T: Collection<E>> T?.withNullOrEmpty(func: () -> Unit): Unit {
if (this == null || this.isEmpty()) {
func()
}
}
Evitaría encadenar esto porque entonces está reemplazando una declaración if
o when
con algo más prolijo. Y está obteniendo más en el ámbito que brindan las alternativas que menciono a continuación, que es una bifurcación completa para situaciones de éxito / fracaso.
Nota: estas extensiones se generalizaron a todos los descendientes de Collections
con valores no nulos. Y trabajar para algo más que listas.
Alternativas:
La biblioteca de Result para Kotlin ofrece una buena manera de manejar su caso de "haga esto o aquello" según los valores de respuesta. Para Promesas, puedes encontrar lo mismo en la biblioteca de Kovenant .
Ambas bibliotecas le brindan la manera de devolver resultados alternativos desde una sola función, y también para derivar el código en función de los resultados. Requieren que usted controle al proveedor de la "respuesta" sobre la que se actúa.
Estas son buenas alternativas de Kotlin a Optional
y Maybe
.
Explorando las funciones de extensión más lejos (y tal vez demasiado)
Esta sección es solo para mostrar que cuando se topa con un problema como el que se plantea aquí, puede encontrar fácilmente muchas respuestas en Kotlin para que la codificación sea la que usted desea. Si el mundo no es agradable, cambia el mundo. No pretende ser una respuesta buena o mala, sino más bien información adicional.
Si te gustan las funciones de extensión y quieres considerar encadenarlas en una expresión, probablemente las cambiaría de la siguiente manera ...
Los sabores withXyz
para devolver this
y whenXyz
deberían devolver un tipo nuevo que permita que toda la colección se convierta en una nueva (tal vez incluso sin relación con el original). Resultando en código como el siguiente:
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")
}
}
Nota: el código completo para esta versión se encuentra al final de la publicación (1)
Pero también podría ir en una dirección completamente nueva con un mecanismo personalizado "esto de lo contrario":
fun foo(someList: List<String>?) {
someList.whenNullOrEmpty {
// other code
}
.otherwise { list ->
// do something with `list`
}
}
No hay límites, sea creativo y aprenda el poder de las extensiones, pruebe nuevas ideas y, como puede ver, hay muchas variaciones en la forma en que las personas desean codificar este tipo de situaciones. El stdlib no puede soportar 8 variaciones de este tipo de métodos sin ser confuso. Pero cada grupo de desarrollo puede tener extensiones que coincidan con su estilo de codificación.
Nota: el código completo para esta versión se encuentra al final de la publicación (2)
Código de ejemplo 1: Aquí está el código completo para la versión "encadenada":
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
}
Ejemplo de código 2: Aquí está el código completo para una biblioteca de "esto de lo contrario" (con prueba de unidad):
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
}
ACTUALIZAR:
kotlin 1.3 proporciona isNullOrEmpty
ahora!
https://twitter.com/kotlin/status/1050426794682306562
¡prueba esto! muy claro.
var array: List<String>? = null
if (array.orEmpty().isEmpty()) {
// empty
} else {
// not empty
}