Construcción de colecciones

Construcción de colecciones en Kotlin

Después de una primera aproximación a las colecciones, ahora vamos a ver las formas más comunes de crear colecciones en Kotlin.

Para construir una colección es habitual recurrir a funciones de la biblioteca estándar (paquete kotlin.collections) como listOf(), setOf(), mutableListOf() y mutableSetOf(). Si al utilizar estas funciones proporcionamos a la vez los elementos de la colección, no es necesario especificar su tipo, pero cuando creamos colecciones vacías es necesario explicitar el tipo:
val numerosSet = setOf("uno", "dos", "tres", "cuatro")
// es igual a:
// val numerosSet: Set<String> = setOf<String>("uno", "dos", "tres", "cuatro")

val vacioSet = mutableSetOf<String>()

Lo cual también es aplicable a los diccionarios con las funciones mapOf() y mutableMapOf(), cuyas claves y valores se pasan como objetos Pair (un objeto de la clase Pair representa un par genérico de dos valores) creados usualmente utilizando la función infix to (revisar la entrada sobre Funciones infix).
val numerosMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)

Con la clase Pair explícita:
val (key, valor) = Pair("Key3", 3)
val miMap = mapOf(Pair("Key1", 1), Pair("Key2", 2), key to valor)
println("Todas las claves: ${miMap.keys}")    // [key1, Key2, Key3]
println("Todos los valores: ${miMap.values}") // [1, 2, 3]

Utilizar la función to para crear un objeto Pair puede suponer un uso excesivo de memoria, especialmente en situaciones críticas de rendimiento, por lo que en esos casos es recomendable utilizar formas alternativas. Por ejemplo, se podría crear un diccionario mutable y llenarlo con elementos utilizando operaciones de escritura (además la función apply puede facilitar la inicialización en estos casos):
val numerosMap = mutableMapOf<String, Int>().apply {
    this["key1"] = 1;
    this["key2"] = 2;
    this["key3"] = 3
}

También hay funciones para crear colecciones vacías (sin ningún elemento) como emptyList(), emptySet() y emptyMap() y, como dijimos antes, en estos casos se debe especificar el tipo de elementos que contendrá la colección:
val listaVacia = emptyList<String>()

Para las listas (tanto mutables como de solo lectura), hay un constructor que toma el tamaño de la lista y una función de inicialización que define el valor de cada elemento en función de su índice:
val doble = List(4, { it * 2 })
// lista de tamaño 4 en la que cada elemento es igual al índice x 2
println(doble) // [0, 2, 4, 6]

La construcción anterior también se puede escribir así:
val doble = List(4) { it * 2 }

Por otra parte, para crear una colección determinada también podemos usar las implementaciones predeterminadas para cada tipo de colección, como ArrayList y LinkedList para las listas, LinkedHashSet y HashSet para los conjuntos, y LinkedHashMap y HashMap para los diccionarios. Por ejemplo:
import java.util.LinkedList

fun main() {
    val miLista = LinkedList<String>(listOf("uno", "dos", "tres"))
    val miSet = HashSet<Int>(32) // inicial capacidad
}

Otra forma de crear una colección es copiando otra ya creada, utilizando para ello operaciones de copia de la biblioteca estándar como, entre otras, toList(), toMutableList() y toSet(), que crean una nueva colección de los mismos elementos como una instantánea de una colección en un momento determinado. Esto es, crean referencias a los mismos elementos, por lo que un cambio realizado en un elemento de la colección original se refleja en todas sus copias, pero si se agregan o eliminan elementos a la colección original, esto no afectará a las copias. Por su parte, las copias también pueden cambiarse independientemente de la colección original.
val originalLista = mutableListOf(1, 2, 3)
val copiaLista = originalLista.toMutableList()
val lecturaLista = originalLista.toList()

originalLista.add(4)
println("Tamaño lista original: ${originalLista.size}") // Tamaño lista original: 4
println("Tamaño lista copia: ${copiaLista.size}")       // Tamaño lista copia: 3
copiaLista.add(4)
copiaLista.add(5)
println("Tamaño lista copia: ${copiaLista.size}")       // Tamaño lista copia: 5
println("Tamaño lista original: ${originalLista.size}") // Tamaño lista original: 4

// lecturaLista.add(4) // ERROR: lista de solo lectura
println("Tamaño lista inmutable: ${lecturaLista.size}") // Tamaño lista inmutable: 3

Estas funciones también se pueden usar para convertir colecciones a otros tipos, por ejemplo, crear un conjunto a partir de una lista o viceversa:
val originalLista = mutableListOf(1, 2, 3)
val copiaSet = originalLista.toMutableSet()
copiaSet.add(3)
copiaSet.add(4)
println(copiaSet) // [1, 2, 3, 4]

Otra opción es crear una referencia a una instancia de colección. Entonces, cuando la instancia de colección se modifica a través de una referencia, los cambios se reflejan en todas sus referencias.
val originalLista = mutableListOf(1, 2, 3)
val referenciaLista = originalLista
val otraReferencia = originalLista
referenciaLista.add(4)
println("Tamaño lista original: ${originalLista.size}")  // Tamaño lista original: 4
println("Tamaño otraReferencia: ${otraReferencia.size}") // Tamaño otraReferencia: 4

Esto se puede utilizar para restringir la posibilidad de modificar una colección. Por ejemplo, si a partir de una lista mutable creamos una referencia en una lista de solo lectura, no podremos modificar la colección a través de esta referencia, aunque esta referencia si mostrará el estado de la lista original en cada momento:
val originalLista = mutableListOf(1, 2, 3)
val referenciaLista: List<Int> = originalLista
//referenciaLista.add(4) // ERROR
originalLista.add(4)
println(referenciaLista) // [1, 2, 3, 4]

Por último, otra manera de crear colecciones que veremos en muchas ocasiones es operando sobre otras colecciones, tanto mutables como inmutables, utilizando diversas funciones de mapping (map, mapIndexed), de filtrado (filter), o de asociación (associateWith) entre otras, las cuales devuelven como resultado nuevas colecciones. Aunque volveremos a tratar sobre estas funciones más adelante, vamos a verlas en acción en el siguiente ejemplo:
val numerosLista = listOf("cero", "uno", "dos", "tres", "cuatro")
val masDeTresLetras = numerosLista.filter { it.length > 3 }
println(masDeTresLetras) // [cero, tres, cuatro]

val numerosSet = setOf(1, 2, 3)
val porTres = numerosSet.map { it * 3 }
println(porTres) // [3, 6, 9]
val indicePorValor = numerosSet.mapIndexed { idx, value -> idx * value }
println(indicePorValor) // [0, 2, 6]

val numerosInt = listOf("uno", "dos", "tres", "cuatro")
val dicNumerosLetras = numerosInt.associateWith { it.length } // Map<String, Int>
println(dicNumerosLetras) // {uno=3, dos=3, tres=4, cuatro=6}

Comentarios

Entradas populares

I/O: entrada y salida de datos en consola

Recursos gratis para aprender Kotlin

Lectura y escritura de archivos