Colecciones en Kotlin: introducción

video[Tutorial Colecciones en Kotlin: introducción

Anteriormente, en este tutorial hemos visto cómo declarar rangos en Kotlin mediante los operadores .., o su equivalente rangeTo(), downTo(), o in. Todos ellos trabajan con Iterables, una de las grandes mejoras que presenta Kotlin respecto a Java.
Las colecciones son usadas para almacenar grupos de objetos. Vamos a poder almacenar, recuperar u organizar objetos utilizando una estructura de datos determinada. Kotlin proporciona su API de colecciones como una librería estándar construida encima del API de colecciones de Java.
Hay que indicar que estas librerías son enlazadas a sus implementaciones en tiempo de compilación y, al contrario que otras partes de Kotlin, no podemos ver el código fuente de su implementación, porque sus colecciones son de hecho implementadas por las colecciones Java estándares tales como ArrayList, Maps, HashMap, Set, HashSet, List, etc.
En Kotlin encontramos dos clases de colecciones: las mutables, las cuales nos permiten modificarlas ya sea añadiendo, eliminando o reemplazando un elemento, y las inmutables, que no podrán ser modificadas. En comparación con Java, podremos hacer mucho más con menos código (figura 1).

Nota: En realidad podremos añadir, eliminar o modificar elementos de una colección inmutable mediante funciones de extensión (también conocidas como funciones de operador), pero estas terminarán generando una nueva colección.

Como se muestra a la derecha, si queremos permitir que una variable pueda ser null, tendremos que definirla añadiendo ? a su tipo de datos.
Dado que los errores de tipo NullPointerException se producen cuando intentamos acceder a un método o una propiedad de una variable nula, Kotlin evitará dichos errores denegando la llamada a los métodos o el acceso a las propiedades afectas. Sin embargo, en determinadas ocasiones será necesario utilizar esos métodos o propiedades. Para ello, disponemos de diversas alternativas.

Principales interfaces

La interfaz Iterable Kotlin está encima de la clase de jerarquía de colecciones. Esta interfaz habilita a las colecciones para ser representadas como una secuencia de elementos (que pueden ser iteradas, naturalmente).
interface Iterable<out T> {
   operator fun iterator(): Iterator<T>
}  
La interfaz Collection de Kotlin extiende a la interfaz Iterable, y se trata de una interfaz inmutable (las interfaces Set y List en Kotlin extienden a esta interfaz).
interface Collection<out E> : Iterable<E> {
   override fun iterator(): Iterator<E>
   val size: Int
   fun isEmpty(): Boolean
   operator fun contains(element: E): Boolean
   public fun containsAll(elements: Collection<E>): Boolean
} 
La interfaz MutableIterable nos da un iterador mutable especializado de la interfaz padre Iterable.
interface MutableIterable<out T> : Iterable<T> {
    override fun iterator(): MutableIterator<T>
}  
La interfaz MutableCollection habilita a las colecciones para ser mutables. En otras palabras, nos permite agregar o eliminar componentes en una colección dada. Esta interfaz extiende a la interfaz Collection y la interfaz MutableIterable. Las interfaces MutableSet y MutableList extienden de ella. Como se muestra a continuación, añade a las funciones de Collection nuevas para cambiar elementos:
interface MutableCollection<E>: Collection<E>, MutableItera-ble<E> {
    override fun iterator(): MutableIterator<E>
    fun add(element: E): Boolean
    fun addAll(elements: Collection<E>): Boolean
    fun remove(element: E): Boolean
    fun removeAll(elements: Collection<E>): Boolean
    fun retainAll(elements: Collection<E>): Boolean
    fun clear()
}  

Funciones de extensión sobre colecciones

En Java disponíamos de gran cantidad de métodos estáticos para trabajar con colecciones (java.util.Collections). Kotlin nos proporciona las funciones de extensión que nos permiten usar una sintaxis mucho más intuitiva.
Collections.swap(lista, 2, 5); 
lista.swap(2, 5)  
Además, el uso de lambdas nos va a proporcionar mucha potencia para mani-pular listas. Echemos un vistazo a algunas de las funciones disponibles:
  •     last() devuelve el último elemento en una lista o conjunto.
  •   first() devuelve el primer elemento de una colección.
  •   count() cuenta los elementos de una colección.
También podemos proveer un predicado para buscar dentro de un subconjunto de elementos.
val lista: List<String> = listOf("uno", "dos", "tres")
println(lista.last())  // "tres"
println(lista.last{ it.length == 3 }) // "dos"
println(lista.count()) // 3
println(lista.count{ it.length == 3 }) // 2
val conjunto: Set<Int> = setOf(3, 6, 5, 5, 5, 3)
println(conjunto.last()) // 5 
Nota: En un conjunto last() devuelve el último elemento añadido. El último 3 no se ha añadido dado que ya estaba en el conjunto.
  •    all() devuelve true si todos los elementos cumplen una condición.
  •    any() devuelve true si al menos un elemento cumple una condición.
  •    none() devuelve true si ningún elemento cumple una condición.
val mapa: Map<Int, String> = mapOf(1 to "uno", 2 to "dos", 3 to "tres")
if (mapa.all{ it.key < 5}) println("todas la claves < 5")
if (mapa.any{ it.value == "dos"}) println("un valor es 'dos'") 
  •    drop() devuelve una nueva lista o conjunto conteniendo todos los elementos excepto los primeros n elementos.
val lista: List<String> = listOf("uno", "dos", "tres")
print(lista.drop(2)) // "tres" 
  •    plus() devuelve una colección conteniendo todos los elementos del original y después el elemento dado si no está ya en la colección. Esto terminará creando una nueva lista en vez de modificar la existente.
  •    minus() devuelve una nueva colección conteniendo todos los elementos del conjunto original excepto el elemento dado.
  •    max() devuelve el elemento más grande de la colección.
  •    average() devuelve el valor promedio de los elementos en la colección.
val lista: List<Int> = listOf(1, 3, 4)
println(lista.plus(6))   // [1, 3, 4, 6]
println(lista.minus(3))  // [1, 4]
println(lista.max())       // 4
println(lista.average()) // 2.6666666666666665 
Aquí solo hemos expuesto un pequeño conjunto de funciones. Si quieres conocer todas las funcionalidades que ofrecen las colecciones de Kotlin, te reco-mendamos que consultes su documentación oficial.