Mapas En Kotlin

En este tutorial verás la definición y uso de mapas en Kotlin. Verás la declaración de la interfaz Map, como crear mapas de solo lectura, mapas mutables y como iterar sobre sus entradas en un bucle for.

Mapas

Un mapa es una colección que almacena sus elementos (entradas) en forma de pares clave-valor.

Esto quiere decir que a cada clave le corresponde un solo valor y será única como si se tratase de un identificador.

Ilustración de mapas en Kotlin

La ilustración anterior muestra la correspondencia entre una colección de claves a una de valores. Por ejemplo, la entrada «Name» -> «Catrina sería un habitante del mapa.

Crear Mapas De Solo Lectura

La interfaz que representa a los mapas en Kotlin es Map<K,V>. Donde los parámetros de tipo K y V representan a los tipos para claves (propiedad keys) y valores (propiedad values).

interface Map<K, out V>

Esta definición solo te provee acceso de solo lectura, por lo que solo podrás usar comportamientos de consulta.

Para crear un mapa de solo lectura usa una de las formas de la función mapOf():

// Múltiples pares
fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V> 
// Mapa vacío
fun <K, V> mapOf(): Map<K, V>
// Un solo par
fun <K, V> mapOf(pair: Pair<K, V>): Map<K, V>

Como ves, las entradas del mapa se generan con la clase Pair, la cual representa un par de valores genérico.

Por ejemplo, si creas un mapa para cargar los ajustes de un usuario en tu aplicación:

fun main() {
    val userSettings: Map<String, String> = mapOf(
        "name" to "Catrina",
        "language" to "Español",
        "logo" to "logo.png",
        "website" to "www.site.com"
    )

    println("$userSettings")
    // {name=Catrina, language=Español, logo=logo.png, website=www.site.com}
}

La función de extensión to() usa el modificador infix para permitirte crear pares de forma legible:

infix fun <A, B> A.to(that: B): Pair<A, B>

Sin embargo, también es posible crear el par con el constructor de la forma Pair("name", "Catrina").

Operaciones De Lectura

Los siguientes son atributos y métodos que te permiten consultar el estado de tus mapas:

  • entries: retorna un tipo Set<Entry<K,V>> de solo lectura de todos los pares clave-valor
  • keys: retorna un Set<K> de solo lectura de todas las claves
  • size: retorna el número de entradas en el mapa
  • values: retornar una Collection<V> de solo lectura con los valores en el mapa

Si imprimes todas las propiedades del ejemplo anterior tendrás lo siguiente:

println(userSettings.size) // 4
println(userSettings.entries) // [name=Catrina, language=Español, logo=logo.png, website=www.site.com]
println(userSettings.keys) // [name, language, logo, website]
println(userSettings.values) // [Catrina, Español, logo.png, www.site.com]

Por el lado de los métodos de lectura tienes a:

  • mapa[clave]: Esta sintaxis permite obtener el valor a partir de la clave en el corchete. Es la construcción equivalente al operador get(clave)
  • getOrDefault(key, defaultValue): Obtiene el valor correspondiente a la clave, de lo contrario retorna a defaultValue
  • isEmpty(): Retorna true si el mapa no contiene entradas y false en caso contrario
  • containsKey(key): Retorna true si key existe en el mapa. Esto es equivalente a usar el operador in al comprar la clave frente al mapa
  • containsValue(value): Retorna true si una o varias claves se relacionan con value

Observa algunos ejemplos:

println(userSettings["logo"]) // logo.png
println(userSettings.get("web")) // null
println(userSettings.getOrDefault("email", "Sin email")) // Sin email
println(userSettings.isEmpty()) // false
println("name" in userSettings) // true
println("Español" in userSettings.values ) // true

El tipo de retorno del operador get() es anulable, por lo que si no encuentra el elemento obtendrás null como se muestra en la línea 2.

Tanto containsKey() como containsValue() pueden reemplazarse con el operador in.

Crear Mapas Mutables

Los mapas mutables te otorgan el poder de usar comandos de operaciones sobre los elementos como agregar, actualizar y remover entradas. El diseño de esta figura la encuentras en la interfaz MutableMap<K,V>, la cual extiende de Map<K,V>.

interface MutableMap<K, V> : Map<K, V>

Crea una instancia de un mapa mutable con el método mutableMapOf(). Al igual que mapOf() recibe argumentos variables para los pares iniciales, o puedes crear una definición vacía:

fun <K, V> mutableMapOf(): MutableMap<K, V>
fun <K, V> mutableMapOf(
    vararg pairs: Pair<K, V>
): MutableMap<K, V>

Por ejemplo, creemos un mapa que contenga el año de publicación de varios libros:

fun main() {
    val booksMap = mutableMapOf(
        "Sinsajo" to 2010,
        "Yo, Robot" to 1950,
        "Crimen y castigo" to 1935,
        "Cien años de soledad" to 1991
    )

    println(booksMap)
    // {Sinsajo=2010, Yo, Robot=1950, Crimen y castigo=1935, Cien años de soledad=1991}
}

Veamos como modificar su estructura.

Añadir Y Actualizar Entradas

Usa el método put(key, value) para asociar la clave key con el valor value. Si la clave no existe la entrada es añadida al mapa, de lo contrario el valor es actualizado.

booksMap.put("La máquina del tiempo", 1890)
booksMap["La máquina del tiempo"] = 1895
println(booksMap)
// {Sinsajo=2010, Yo, Robot=1950, Crimen y castigo=1935, Cien años de soledad=1991, La máquina del tiempo=1895}

Sin embargo IntelliJ IDEA te recomendará usar al operador [ ] junto con la clave para añadir o actualizar.

Remover Entradas

En este caso usa el método remove(key) para remover la entrada del mapa. Por ejemplo, eliminemos la entrada para "Sinsajo":

booksMap.remove("Sinsajo")
println(booksMap)
// {Yo, Robot=1950, Crimen y castigo=1935, Cien años de soledad=1991, La máquina del tiempo=1895}

Otra variante del método es remove(key, value) donde se remueve el elemento con la clave key solo si su valor actual es value. El retorno será true si es eliminado o false en caso negativo.

println(booksMap.remove("Cien años de soledad", 2015)) // false

Recorrer Un Mapa

Debido a la naturaleza de los mapas en Kotlin, es posible desestructurar las declaraciones que comprometan a sus entradas en valores individuales.

Un ejemplo claro de esto es recorrer sobre los elementos de un mapa en un bucle for:

fun main() {
    val operationsMap = mapOf(
        "Suma" to '+',
        "Resta" to '-',
        "Multiplicación" to 'x',
        "División" to '÷'
    )

    for ((operation, symbol) in operationsMap) {
        println("$operation -> $symbol")
    }
}

Salida:

Suma -> +
Resta -> -
Multiplicación -> x
División -> ÷

Como ves, convertimos a la sintaxis (operation, symbol) cada entrada de operationsMap, con el fin de utilizar ambos elementos en el cuerpo del bucle.

Esto también es aplicable para la declaración de lambdas. Es posible expresar como lista de parámetros el par clave-valor. Por ejemplo, si usamos la función forEach() sobre el mapa para imprimir su contenido:

operationsMap.forEach { (k, v) -> println("$k -> $v") }

Pasamos como sección de parámetros el combinado (k, v) para que el cuerpo del lambda imprima en cada iteración a la entrada.

Ú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!