Manera idiomática de manejar lists anulables o vacías en Kotlin

Supongamos que tengo activities variables de tipo List<Any>? . Si la list no es nula y no está vacía, quiero hacer algo; de lo contrario, quiero hacer algo más. Se me ocurrió la siguiente solución:

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

¿Hay una forma más idiomática de hacer esto en Kotlin?

    Para algunas acciones simples puede usar el operador de llamada segura, asumiendo que la acción también respeta no operar en una list 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 function de extensión – dos variaciones dependiendo de si desea recibir la list como this o como un 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 la function inversa:

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

    Evitaría encadenar estos porque entonces estás reemplazando una statement de if o when con algo más prolijo. Y se está adentrando más en el ámbito que proporcionan las alternativas que menciono a continuación, que es una ramificación completa para situaciones de éxito / fracaso.

    Nota: estas extensiones se generalizaron a todos los descendientes de Collections tienen valores no nulos. Y trabaje por algo más que solo Listas.

    Alternativas:

    La biblioteca de resultados de Kotlin ofrece una buena manera de manejar su caso de "hacer esto o aquello" en function de los valores de respuesta. Para Promises, puede encontrar lo mismo en la biblioteca de Kovenant .

    Ambas bibliotecas le dan la manera de devolver resultados alternativos de una sola function, y también de derivar el código en function de los resultados. Requieren que controle al proveedor de la "respuesta" que se aplica.

    Estas son buenas alternativas de Kotlin a Optional y Maybe .

    Explorando las funciones de extensión adicional (y tal vez demasiado)

    Esta sección es solo para mostrar que cuando se aborda un problema como el que se plantea aquí, puede encontrar fácilmente muchas respuestas en Kotlin para hacer la encoding de la manera que desee. Si el mundo no es agradable, cambia el mundo. No es una buena o mala respuesta, sino información adicional.

    Si te gustan las funciones de extensión y quieres considerar encadenarlas en una expresión, probablemente las cambie de la siguiente manera …

    Los sabores withXyz para devolver this y whenXyz debería devolver un nuevo tipo que permite que toda la colección se convierta en una nueva (tal vez incluso sin relación con el original). Resultando en un 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 de "esto de otra manera":

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

    No hay límites, sé creativo y aprende el poder de las extensiones, testing nuevas ideas y, como puedes ver, hay muchas variaciones de cómo la gente quiere codificar este tipo de situaciones. El stdlib no puede soportar 8 variaciones de este tipo de methods sin ser confuso. Pero cada grupo de desarrollo puede tener extensiones que coincidan con su coding style.

    Nota: el código completo para esta versión se encuentra al final de la publicación (2)

    Código de muestra 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 } 

    Código de muestra 2: aquí está el código completo para una biblioteca "this that that that" (con testing unitaria):

     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 } 

    Considere usar ?.forEach si es apropiado

     activities?.forEach { doSmth(it) } 

    Si desea exactamente el comportamiento que describió, creo que su variante es mejor que cualquier otra cosa más concisa que se me ocurra. (Sin embargo, simple, if debería ser suficiente)

    Además de las otras respuestas, también puede usar 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ás 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 } 

    En primer lugar, quería recomendar la function de extensión además de la respuesta de @ mlatu, que maneja la condición 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 Mónada Maybe