Desestructuración En Kotlin

En este tutorial aprenderás el uso de la desestructuración en Kotlin con el fin desempacar un solo valor compuesto en múltiples variables a través de las funciones de componentes. Para ello te presentaremos diferentes ejemplos de declaraciones de desestructuración con bucles for, clases de datos, mapas y lambdas.

Declaraciones De Desestructuración

Kotlin te provee una característica que te permite crear múltiples variables a la vez a partir de un valor compuesto. Dicha sintaxis recibe el nombre de declaración de desestructuración.

Por ejemplo: Desestructurar un par que contiene el nombre y saldo de una cuenta.

fun main() {
    val account = "Paypal" to 50.4
    val (name, balance) = account
    println("Cuenta: $name")
    println("Saldo: $balance")
}

Como ves, a partir de un objeto se crea una declaración con val o var de un grupo de múltiples variables en paréntesis.

Funciones De Componentes

La desestructuración es posible gracias a la existencia de funciones de componentes o funciones componentN(). Internamente el compilador invoca a estas funciones desde el objeto a desestructurar, con el objetivo de declarar e inicializar múltiples variables.

De ahí la capacidad para poder usarlas de formas independiente, ya que una desestructuración internamente representa la asignación de cada función de componente. Donde la posición de la variable es el índice N de componentN().

Por ejemplo: Desestructurar el par de la cuenta pero con funciones de componente.

fun main() {
    val account = "Paypal" to 50.4
    val name = account.component1()
    val balance = account.component2()
}

Sintaxis De Guion Bajo Para Variables Sin Usar

En ocasiones no necesitaras recolectar el valor de una variable a partir de una desestructuración. Por lo que puedes hacer uso del guion bajo para representar tu intención de no usarlo.

Ejemplo: Desestructurar las coordenadas de un punto C (2, 0, 4) en el espacio e imprimir el componente y.

fun main() {
    val center = Triple(2, 0, 4)
    val(_,y,_) = center
    println(y) // 0
}

Claramente esta sintaxis le dice al compilador que no se debe llamar a la función de componente con la posición del valor ignorado.

Declaraciones De Desestructuración En Bucles

Desestructurar un objeto es aplicable en toda gramática donde exista una sección para declarar variables. Uno de estos casos es el bucle for, el cual usa una declaración de la variable de iteración.

Ejemplo: Recorrer un arreglo de números enteros con un bucle for e imprimir solo los valores en posiciones pares.

fun main() {
    val numbers = intArrayOf(1, 5, 4, 3, 0, 7)
    for ((index, number) in numbers.withIndex()) {
        if ((index + 1) % 2 == 0)
            println(number)
    }
}

En el código anterior, la función de extensión withIndex() retorna en un iterable del tipo IndexedValue. Una clase que tiene funciones de componentes para el índice del arreglo y el valor.

Esto permite desestructurar cada variable de iteración en index y number.

Desestructuración En Clases De Datos

Recuerda que a las clases de datos se les agrega funciones de componente automáticamente por las propiedades de constructor que posean. Lo que quiere decir que el hecho de marcarlas con data te habilita para desestructurar sus instancias.

Ejemplo: Desestructurar una clase de datos con dos propiedades sobre un troll.

data class Troll(val health: Int, val speed: Double)

fun main() {
    val (health, speed) = Troll(150, 0.5)    
}

También es posible conseguir este comportamiento si no es una data class. Solo debes añadir funciones de operador con la convención del nombrado para retornar los componentes que establezcas.

Ejemplo: Refactorizar la clase Troll para eliminar el modificador data y definir funciones de componentes para las dos propiedades.

class Troll(val health: Int, val speed: Double){
    operator fun component1() = health
    operator fun component2() = speed
}

fun main() {
    val (health, speed) = Troll(150, 0.5)
    println("El troll tiene $health puntos de vida y se mueve a $speed")
}

Desestructuración En Mapas

Los mapas también pueden ser desestructurar sus entradas, ya que la librería estándar de Kotlin les ha otorgado extensiones para las funciones de componentes.

operator fun <K, V> Map<K, V>.iterator(): Iterator<Map.Entry<K, V>> = entrySet().iterator()
operator fun <K, V> Map.Entry<K, V>.component1() = getKey()
operator fun <K, V> Map.Entry<K, V>.component2() = getValue()

La función de extensión iterator() permite al mapa ser recorrido en un bucle for, ya que produce la variable de iteración en la sentencia. Lo que nos lleva a extraer la clave y valor de cada entrada por medio de component1() y component2().

Por ejemplo: Imprimir las entradas de un mapa que contiene pares para champús y tipos de tratamiento.

fun main() {
    val shampoosAndTreatmentTypes = mapOf(
        "Buticaba" to "Liso", "Pinorelo" to "Crespo",
        "Curucuru" to "Ondulado", "Mertabé" to "Caspa"
    )

    for ((shampoo, treatment) in shampoosAndTreatmentTypes) {
        println("El shampoo $shampoo es para el tratamiento $treatment")
    }
}

Desestructuración En Lambdas

En el tutorial de funciones lambda vimos que estos literales de función están compuestos por una lista de parámetros y un cuerpo. Precisamente esa lista de parámetros al permitir una declaración de múltiples variables es la que habilita el llamado a funciones componentN().

De esta forma es posible desestructurar una lambda y usar las variables directamente en el cuerpo.

Ejemplo: Reescribir el ejemplo anterior con la función de extensión forEach().

fun main() {
    val shampoosAndTreatmentTypes = mapOf(
        "Buticaba" to "Liso", "Pinorelo" to "Crespo",
        "Curucuru" to "Ondulado", "Mertabé" to "Caspa"
    )

    shampoosAndTreatmentTypes.forEach { (shampoo, treatment) ->
        println("El shampoo $shampoo es para el tratamiento $treatment")
    }
}

En este caso, forEach() recibe un lambda para ejecutar una función lambda en cada iteración. El valor que recibe en su contexto es la entrada del mapa, por lo que es posible crear una lista de parámetros con la desestructuración de las instancias Map.Entry<K, V>.

Únete Al Discord De Develou

Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!