Colecciones Mutables
⏱ Dedicación recomendada: 0 minutos
Esto considera el contenido visible y relevante, e ignora texto colapsado o marcado como opcional.
r8vnhill/collections-kt
En Kotlin, las colecciones mutables son estructuras de datos que permiten modificar su contenido después de ser creadas. Son útiles cuando se requiere agregar, eliminar o actualizar elementos en tiempo de ejecución. Todas las colecciones estándar de Kotlin, como listas, conjuntos y mapas, tienen versiones mutables que proporcionan métodos adicionales para modificar su contenido.
Aunque las colecciones mutables y las inmutables comparten interfaces similares, solo las mutables permiten modificar su contenido directamente.
Es importante notar que una colección mutable no necesita ser asignada a una variable var
para permitir modificaciones. Aunque la referencia esté declarada como val
, el contenido de la colección sigue siendo mutable. Asignar colecciones mutables a val
ayuda a prevenir cambios accidentales en la referencia, lo que mejora la seguridad y la mantenibilidad del código a medida que este crece en complejidad. Sin embargo, si se intenta reasignar una colección declarada con val
, se producirá un error de compilación. Por lo tanto, se recomienda usar val
siempre que sea posible para garantizar un código más seguro y robusto.
MutableIterator
y MutableIterable
Un MutableIterator es una interfaz que permite recorrer y modificar elementos en una colección mutable. A diferencia de un iterador estándar, un MutableIterator
permite agregar y eliminar elementos durante la iteración.
interface MutableIterator<out T> : Iterator<T> {
fun remove(): Unit
}
Un MutableIterable es una interfaz que extiende Iterable
y permite obtener un iterador mutable para recorrer y modificar los elementos de la colección.
interface MutableIterable<out T> : Iterable<T> {
fun iterator(): MutableIterator<T>
}
Ejemplo
- Código esencial
- Código completo
val numbers: MutableIterable<Int> =
mutableListOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val iterator = numbers.iterator()
while (iterator.hasNext()) {
if (iterator.next() % 2 == 0) {
iterator.remove()
}
}
numbers shouldBe listOf(1, 3, 5, 7, 9)
package com.github.username.collections.mutable
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe
class MutableIterableTest : FreeSpec({
"A mutable iterable" - {
"when removing all even numbers" - {
"should have only odd numbers" {
val numbers: MutableIterable<Int> =
mutableListOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val iterator = numbers.iterator()
while (iterator.hasNext()) {
if (iterator.next() % 2 == 0) {
iterator.remove()
}
}
numbers shouldBe listOf(1, 3, 5, 7, 9)
}
}
}
})
MutableCollection
Una MutableCollection es una colección iterable que permite modificar su contenido. Además de las propiedades estándar de una colección, como size
y contains
, las colecciones mutables incluyen métodos para agregar y eliminar elementos.
interface MutableCollection<E> : Collection<E>, MutableIterable<E> {
fun add(element: E): Boolean
fun remove(element: E): Boolean
fun addAll(elements: Collection<E>): Boolean
fun removeAll(elements: Collection<E>): Boolean
fun retainAll(elements: Collection<E>): Boolean
fun clear(): Unit
}
Ejemplo
- Código esencial
- Código completo
val mutableStrings: MutableCollection<String> =
mutableListOf("Kotlin", "Java")
mutableStrings += "Scala"
mutableStrings shouldBe listOf("Kotlin", "Java", "Scala")
package com.github.username.collections.mutable
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe
class MutableCollectionTest : FreeSpec({
"A mutable collection" - {
"when adding elements" - {
"should contain the added elements" {
val mutableStrings: MutableCollection<String> =
mutableListOf("Kotlin", "Java")
mutableStrings += "Scala"
mutableStrings shouldBe listOf("Kotlin", "Java", "Scala")
}
}
}
})
MutableList
Una MutableList es una lista ordenada que permite modificar los elementos en una posición específica, agregar o eliminar elementos. Es útil cuando se necesita acceso directo mediante índices y operaciones de manipulación de datos.
interface MutableList<E> : List<E>, MutableCollection<E> {
fun addAll(index: Int, elements: Collection<E>): Boolean
operator fun set(index: Int, element: E): E
fun add(index: Int, element: E): Unit
fun removeAt(index: Int): E
override fun listIterator(): MutableListIterator<E>
override fun listIterator(index: Int): MutableListIterator<E>
override fun subList(fromIndex: Int, toIndex: Int): MutableList<E>
}
Ejemplo
- Código esencial
- Código completo
val mutableList: MutableList<String> =
mutableListOf("Kotlin", "Java", "Scala")
mutableList[1] = "Groovy"
mutableList shouldBe listOf("Kotlin", "Groovy", "Scala")
package com.github.username.collections.mutable
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe
class MutableListTest : FreeSpec({
"A mutable list" - {
"when setting an element at an index" - {
"should replace the element at the index" {
val mutableList: MutableList<String> =
mutableListOf("Kotlin", "Java", "Scala")
mutableList[1] = "Groovy"
mutableList shouldBe listOf("Kotlin", "Groovy", "Scala")
}
}
}
})
MutableSet
Un MutableSet es una colección que no permite elementos duplicados, pero permite modificar su contenido agregando o eliminando elementos. Los sets no garantizan un orden específico de los elementos.
interface MutableSet<E> : Set<E>, MutableCollection<E>
Ejemplo
- Código esencial
- Código completo
val mutableSet: MutableSet<String> =
mutableSetOf("Kotlin", "Java")
mutableSet += "Scala"
mutableSet shouldBe setOf("Kotlin", "Java", "Scala")
package com.github.username.collections.mutable
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe
class MutableSetTest : FreeSpec({
"A mutable set" - {
"when adding elements" - {
"should contain the added elements" {
val mutableSet: MutableSet<String> =
mutableSetOf("Kotlin", "Java")
mutableSet += "Scala"
mutableSet shouldBe setOf("Kotlin", "Java", "Scala")
}
}
}
})
MutableMap
Un MutableMap es una colección que asocia claves (keys) con valores (values), permitiendo modificar los pares clave-valor. Cada clave puede estar asociada a un solo valor, pero se pueden agregar, eliminar o modificar entradas en el mapa.
interface MutableMap<K, V> : Map<K, V> {
fun put(key: K, value: V): V?
fun remove(key: K): V?
fun remove(key: K, value: V): Boolean
fun putAll(from: Map<out K, V>): Unit
fun clear(): Unit
interface MutableEntry<K, V> : Map.Entry<K, V> {
fun setValue(newValue: V): V
}
}
Ejemplo
- Código esencial
- Código completo
val mutableMap: MutableMap<String, String> =
mutableMapOf("kotlin" to "JVM")
mutableMap["java"] = "JVM"
mutableMap["scala"] = "JVM"
mutableMap shouldBe mapOf(
"kotlin" to "JVM",
"java" to "JVM",
"scala" to "JVM"
)
package com.github.username.collections.mutable
import io.kotest.core.spec.style.FreeSpec
import io.kotest.matchers.shouldBe
class MutableMapTest : FreeSpec({
"A mutable map" - {
"when adding elements" - {
"should contain the added elements" {
val mutableMap: MutableMap<String, String> =
mutableMapOf("kotlin" to "JVM")
mutableMap["java"] = "JVM"
mutableMap["scala"] = "JVM"
mutableMap shouldBe mapOf(
"kotlin" to "JVM",
"java" to "JVM",
"scala" to "JVM"
)
}
}
}
})
Ventajas y desventajas de las colecciones mutables
Beneficios
- Flexibilidad y dinamismo: Las colecciones mutables permiten agregar, eliminar y modificar elementos en tiempo de ejecución, lo que es útil en escenarios donde los datos cambian frecuentemente o deben ser actualizados dinámicamente.
- Interfaz consistente: Kotlin proporciona interfaces y métodos comunes para todas las colecciones mutables, facilitando el aprendizaje y el uso uniforme de estas estructuras en diferentes contextos.
- Compatibilidad con APIs Java: Las colecciones mutables en Kotlin son interoperables con las colecciones de Java, lo que permite integrarlas sin problemas en proyectos que utilizan bibliotecas o componentes basados en Java.
- Optimización de recursos: Al modificar directamente las colecciones sin necesidad de crear nuevas instancias, las colecciones mutables permiten optimizar el uso de memoria y mejorar el rendimiento en ciertas operaciones.
Limitaciones
- Peligro de modificación inesperada: Las colecciones mutables pueden ser modificadas accidentalmente en diferentes partes del código, lo que puede llevar a errores difíciles de rastrear y comportamientos inesperados en tiempo de ejecución.
- Menor seguridad y previsibilidad: En comparación con las colecciones inmutables, las mutables ofrecen menos garantías en cuanto a la inmutabilidad de los datos, lo que puede dificultar el razonamiento sobre el estado del sistema y las operaciones concurrentes.
- Propenso a errores de sincronización: En contextos multihilo, las colecciones mutables requieren sincronización manual o uso de colecciones especiales (como
ConcurrentHashMap
) para evitar errores como condiciones de carrera, lo que añade complejidad al desarrollo. - Menor control sobre el flujo de datos: Las colecciones mutables no favorecen la programación funcional, ya que permiten cambiar el estado interno de manera libre, haciendo que sea más difícil de depurar y probar, especialmente en arquitecturas que buscan inmutabilidad y transparencia referencial.
¿Qué aprendimos?
En esta lección, exploramos las colecciones mutables en Kotlin y sus aplicaciones en la programación.
Puntos clave
- Flexibilidad y Dinamismo: Las colecciones mutables permiten modificar su contenido en tiempo de ejecución, lo que las hace ideales para escenarios donde los datos cambian con frecuencia.
- Interfaces Consistentes: Kotlin ofrece una interfaz uniforme para todas las colecciones mutables, lo que simplifica su uso y permite a los desarrolladores aprender a manejarlas de manera eficiente.
- Interoperabilidad con Java: Las colecciones mutables en Kotlin se integran perfectamente con las APIs de Java, lo que facilita su uso en proyectos que combinan ambos lenguajes.
- Riesgos y Complejidad: A pesar de sus ventajas, las colecciones mutables presentan desafíos, especialmente en entornos multihilo y en arquitecturas que favorecen la inmutabilidad. Es importante ser consciente de estas limitaciones para tomar decisiones informadas sobre cuándo y cómo utilizarlas.
Al comprender las ventajas y desventajas de las colecciones mutables, podemos tomar decisiones más estratégicas en la arquitectura y diseño de software, asegurando que el uso de estas colecciones sea apropiado para los requisitos específicos de cada proyecto.
Bibliografías Recomendadas
- 📚 "Chapter 9. Collections: Get Organized". (2019). Dawn Griffiths & David Griffiths, en Head First Kotlin, (pp. 439-502.) O’Reilly.