1. Introducción
Dicho de forma sencilla, una colección mutable es aquella que se puede cambiar después de crearla: añadir, eliminar y modificar elementos. Una colección inmutable es una colección que no se puede modificar después de creada. Igual que el hormigón una vez fraguado: puedes mirarlo, puedes tocarlo, pero ya no podrás moldear nuevas figuras.
Una colección mutable es como un cuaderno con lápiz: escribes, borras, añades notas nuevas. Una colección inmutable es como una página que has plastificado: ahora nadie podrá añadir ni borrar nada.
Ejemplos de colecciones mutables (mutable)
En Java, casi todas las colecciones estándar son mutables por defecto. Son clases como:
- ArrayList
- LinkedList
- HashSet
- TreeSet
- HashMap
- LinkedHashMap
- y muchos otros
Ejemplo: ArrayList
import java.util.*;
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.set(1, "Charlie"); // reemplazamos a Bob por Charlie
names.remove("Alice"); // eliminamos a Alice
System.out.println(names); // [Charlie]
Aquí podemos hacer lo que queramos con la colección: añadir, eliminar, cambiar elementos de sitio. Esto es útil cuando la colección se construye de forma dinámica, por ejemplo, al leer datos de un fichero o de la entrada del usuario.
2. Ejemplos de colecciones inmutables (immutable)
Con estos elementos, que aparecieron en Java 9, seguramente ya te hayas topado, pero todavía hay que acostumbrarse a ellos:
- List.of(...)
- Set.of(...)
- Map.of(...)
- List.copyOf(collection)
- Set.copyOf(collection)
- Map.copyOf(map)
Ejemplo: List.of
List<String> planets = List.of("Mercury", "Venus", "Earth", "Mars");
System.out.println(planets); // [Mercury, Venus, Earth, Mars]
planets.add("Jupiter"); // ¡Lanzará UnsupportedOperationException!
Un intento de modificar la colección provoca una excepción en tiempo de ejecución.
Ejemplo: Collections.unmodifiableList
List<String> modifiable = new ArrayList<>(List.of("a", "b"));
List<String> unmodifiable = Collections.unmodifiableList(modifiable);
unmodifiable.add("c"); // UnsupportedOperationException!
Pero aquí hay truco: si cambias la colección original, el envoltorio también cambiará.
modifiable.add("c");
System.out.println(unmodifiable); // [a, b, c] — ¡apareció el elemento!
3. Diferencias principales entre colecciones mutables e inmutables
| Propiedad | Mutables (mutable) | Inmutables (immutable) |
|---|---|---|
| ¿Se puede añadir un elemento? | Sí | No |
| ¿Se puede eliminar un elemento? | Sí | No |
| ¿Se puede modificar un elemento? | Sí (por ejemplo, set) | No |
| Seguridad en hilos | No (por defecto) | Sí (no hay estado — no hay nada que cambiar) |
| ¿Se puede añadir null? | Sí (normalmente) | No (en los métodos de fábrica de Java 9+) |
| Implementación | ArrayList, HashSet y otros | List.of, Set.of, Map.of, copyOf |
4. ¿Para qué sirven las colecciones inmutables?
De inmediato surge la pregunta: si las colecciones mutables son tan flexibles, ¿para qué necesitamos las inmutables? En realidad hay muchas razones, y todas están relacionadas con la seguridad, la legibilidad y la previsibilidad del código.
Seguridad y protección frente a errores
Cuando expones una colección (por ejemplo, desde un método o una clase), quieres estar seguro de que nadie cambiará accidentalmente su contenido. Esto es especialmente importante si la colección contiene datos «importantes» que no deben modificarse tras la inicialización.
Ejemplo:
public class Team {
private final List<String> players;
public Team(List<String> players) {
// Hacemos una copia inmutable para que nadie pueda cambiar la alineación
this.players = List.copyOf(players);
}
public List<String> getPlayers() {
return players;
}
}
Ahora, cualquier código que reciba la lista de jugadores no podrá añadir allí a su «amigo».
Seguridad en hilos
Las colecciones mutables no son seguras si se accede a ellas desde varios hilos simultáneamente. Las colecciones inmutables, en cambio, se pueden pasar libremente entre hilos: nadie podrá estropearlas.
Facilita la depuración
Si la colección no cambia, siempre sabes qué contiene. No hay que temer que alguien la haya modificado «a escondidas» en otra parte del código.
Uso como claves o valores en otras colecciones
Los objetos inmutables son candidatos ideales para usarse como claves en un Map o como elementos en un Set. Si un objeto puede cambiar después de añadirse, corres el riesgo de perder el acceso a él (véase hashCode y equals).
5. ¿Cuándo es mejor usar colecciones mutables?
Las colecciones mutables son adecuadas cuando:
- La colección se construye por etapas, en un bucle o desde distintas fuentes.
- Se necesitan cambios frecuentes: añadir, eliminar, ordenar.
- La colección está pensada para uso interno y nadie «desde fuera» podrá estropearla.
Ejemplo: construcción de una lista
List<String> shoppingList = new ArrayList<>();
shoppingList.add("Leche");
shoppingList.add("Pan");
shoppingList.add("Manzanas");
// Tras construirla — se puede crear una versión inmutable
List<String> finalList = List.copyOf(shoppingList);
6. ¿Cuándo es mejor usar colecciones inmutables?
- Para almacenar datos constantes (por ejemplo, la lista de días de la semana).
- Para pasar colecciones entre capas de la aplicación (por ejemplo, del DAO al servicio).
- Para devolver colecciones desde métodos, protegiéndolas de modificaciones.
- Para escenarios multihilo, donde la seguridad es importante.
Ejemplo: datos constantes
public static final List<String> WEEKDAYS = List.of(
"Monday", "Tuesday", "Wednesday", "Thursday", "Friday"
);
Ejemplo: exponer hacia el exterior
public List<String> getReadOnlyNames() {
return List.copyOf(names); // nadie podrá modificar la lista
}
7. Peculiaridades y escollos
Inmutabilidad ≠ seguridad en hilos
Una colección inmutable está protegida contra cambios, pero eso no significa que esté protegida contra otros tipos de problemas en entornos multihilo (por ejemplo, si los elementos de la colección son objetos mutables).
List<List<String>> listOfLists = List.of(new ArrayList<>());
listOfLists.get(0).add("Oops!"); // ¡Se puede modificar la lista interna!
Envoltorios vs copias
Como ya se comentó antes, Collections.unmodifiableList es un envoltorio y, si se modifica la colección original, el envoltorio también cambiará. En cambio, List.copyOf crea una copia realmente independiente.
List<String> base = new ArrayList<>(List.of("a", "b"));
List<String> wrap = Collections.unmodifiableList(base);
List<String> copy = List.copyOf(base);
base.add("c");
System.out.println(wrap); // [a, b, c] — ¡cambió!
System.out.println(copy); // [a, b] — ¡se mantuvo igual!
NullPointerException
Los métodos de fábrica (List.of, Set.of, Map.of) no permiten añadir null:
List<String> bad = List.of("a", null); // ¡Lanzará NullPointerException ya al crearse!
8. Comparación de enfoques: pros y contras
Colecciones mutables
La principal virtud de las colecciones mutables es su flexibilidad. Puedes añadir y eliminar elementos sobre la marcha, reconstruir la colección conforme evoluciona la lógica del programa. Este enfoque es especialmente cómodo cuando hay que montar una estructura temporal o cambiar cosas dinámicamente.
Pero la comodidad tiene un precio. Una colección mutable siempre conlleva el riesgo de intervención accidental: alguien en el código puede cambiar los datos sin querer, lo que lleva a errores difíciles de detectar. En programas multihilo la situación es aún peor: cambios paralelos provocan fácilmente condiciones de carrera y fallos inesperados. Controlar el ciclo de vida de esos datos también es más complicado: hay que recordar constantemente quién y cuándo puede modificar la colección.
Colecciones inmutables
Con las colecciones inmutables se trabaja con más tranquilidad. Sabes con certeza que nadie las va a modificar «a tus espaldas». Esto hace el código más seguro, más fácil de depurar y de probar, y permite pasar las colecciones entre hilos sin bloqueos adicionales.
El inconveniente es que a veces hay que sacrificar rendimiento. Si necesitas añadir un elemento nuevo, hay que crear una copia de la colección, lo que implica costes extra de memoria y tiempo. Además, montar estructuras grandes y complejas directamente en forma inmutable puede resultar incómodo. Normalmente se construyen en una colección mutable temporal y, cuando todo está listo, se «congelan».
9. Errores típicos
Error n.º 1: Devolver una colección mutable hacia fuera. Si devuelves desde un método un ArrayList normal, cualquier código externo podrá añadir o eliminar elementos. Esto puede llevar a bugs muy difíciles de rastrear.
Error n.º 2: Usar un envoltorio en lugar de una copia. Si usas Collections.unmodifiableList, pero la colección original se modifica en otro sitio, la «inmutabilidad» es una ilusión.
Error n.º 3: Objetos mutables dentro de colecciones inmutables. Incluso si la colección es inmutable, sus elementos pueden ser mutables. Esto puede provocar cambios de estado inesperados.
Error n.º 4: Intentar añadir null a una colección creada mediante métodos de fábrica. A diferencia de las colecciones antiguas, los nuevos métodos de fábrica no permiten añadir null — obtendrás NullPointerException.
Error n.º 5: Esperar una implementación concreta. Las colecciones creadas mediante List.of o Set.of no garantizan el tipo de implementación (no tiene por qué ser ArrayList o HashSet). No confíes en ello.
GO TO FULL VERSION