Colecciones inmutables (List.of, Set.of, Map.of)

Intermedio
Java
Java
Actualizado: 18/04/2026

Por qué colecciones inmutables

Una colección inmutable es una cuyos elementos no pueden modificarse tras su creación: no se puede añadir, eliminar ni sustituir. Esta propiedad trae beneficios reales:

  • Thread-safe: múltiples hilos pueden compartir sin sincronización.
  • Seguridad: pasarla a APIs no permite que la modifiquen tras tus espaldas.
  • Legibilidad: el contrato queda explícito.
  • Menos memoria: la JVM aplica optimizaciones (no necesita capacidad extra, estructura interna compacta).

Desde Java 9, la API estándar ofrece factorías elegantes para crearlas sin builders ni bucles.

List.of, Set.of, Map.of

Las factorías estáticas devuelven instancias totalmente inmutables:

List<String> colores = List.of("rojo", "verde", "azul");
Set<Integer> primos = Set.of(2, 3, 5, 7, 11);
Map<String, Integer> edades = Map.of("Ana", 30, "Bob", 25);

Propiedades:

  • Inmutables: cualquier intento de modificarlas lanza UnsupportedOperationException.
  • No permiten null: ni elementos null, ni claves/valores null. Lanzan NullPointerException.
  • Sin duplicados en Set.of y claves duplicadas en Map.of: lanzan IllegalArgumentException.
  • Thread-safe.
  • Eficientes: implementaciones especializadas según tamaño (0, 1, 2 o N elementos).

Los elementos sí pueden ser mutables: List.of(new ArrayList<>(...)) tiene una entrada que es una ArrayList modificable. La inmutabilidad es solo de la lista externa.

Map.of hasta 10 entradas

La versión simple de Map.of(k1,v1, k2,v2, ...) acepta hasta 10 entradas:

Map<String, Integer> m = Map.of(
    "uno", 1,
    "dos", 2,
    "tres", 3
);

Para mapas con más entradas, usa Map.ofEntries con Map.entry:

Map<String, Integer> grande = Map.ofEntries(
    Map.entry("enero", 1),
    Map.entry("febrero", 2),
    Map.entry("marzo", 3),
    Map.entry("abril", 4),
    Map.entry("mayo", 5),
    Map.entry("junio", 6),
    Map.entry("julio", 7),
    Map.entry("agosto", 8),
    Map.entry("septiembre", 9),
    Map.entry("octubre", 10),
    Map.entry("noviembre", 11),
    Map.entry("diciembre", 12)
);

List.copyOf, Set.copyOf, Map.copyOf (Java 10+)

Para crear una copia inmutable de una colección existente:

List<String> mutable = new ArrayList<>(List.of("a", "b", "c"));
List<String> inmutable = List.copyOf(mutable);

mutable.add("d");
System.out.println(inmutable); // [a, b, c]: no afectada

Tres optimizaciones importantes:

  • Si la colección fuente ya es inmutable del mismo tipo, copyOf la devuelve tal cual (evita crear una copia innecesaria).
  • Si contiene nulls, lanza NullPointerException (mismas restricciones que of).
  • Usa implementaciones internas compactas.

copyOf es la forma recomendada de aceptar una colección externa y quedarte con una copia inmutable interna (patrón "copia defensiva" para clases inmutables).

Inmutables vs vistas no modificables

Java tiene dos conceptos similares pero distintos:

Vista no modificable (Collections.unmodifiableList(lista)):

  • Envuelve una colección existente.
  • No se puede modificar a través de la vista, pero si modificas la original, la vista refleja el cambio.
List<String> mutable = new ArrayList<>(List.of("a", "b"));
List<String> vista = Collections.unmodifiableList(mutable);
mutable.add("c"); // OK
System.out.println(vista); // [a, b, c]: sí cambia
vista.add("d"); // UnsupportedOperationException

Inmutable de verdad (List.of, List.copyOf):

  • No hay acceso a la fuente original (si se copió).
  • Garantiza que el contenido no cambia bajo ninguna circunstancia.
List<String> mutable = new ArrayList<>(List.of("a", "b"));
List<String> copia = List.copyOf(mutable);
mutable.add("c"); // OK en mutable
System.out.println(copia); // [a, b]: NO afectada
copia.add("d"); // UnsupportedOperationException

La regla práctica: para clases inmutables, usa siempre List.copyOf (o Set.copyOf, Map.copyOf). Para vistas de lectura seguras de objetos que sabes que no cambiarán, unmodifiableList puede bastar.

Ejemplos reales

Constantes inmutables en una clase

public class DiasLaborables {
    public static final List<DayOfWeek> LABORABLES = List.of(
        DayOfWeek.MONDAY,
        DayOfWeek.TUESDAY,
        DayOfWeek.WEDNESDAY,
        DayOfWeek.THURSDAY,
        DayOfWeek.FRIDAY
    );

    public static final Map<String, Integer> MESES_31_DIAS = Map.of(
        "enero", 31, "marzo", 31, "mayo", 31, "julio", 31,
        "agosto", 31, "octubre", 31, "diciembre", 31
    );
}

API que recibe colecciones y las quiere "petrificar"

public final class Pedido {
    private final List<LineaPedido> lineas;

    public Pedido(List<LineaPedido> lineas) {
        this.lineas = List.copyOf(lineas); // inmutable y desconectada
    }

    public List<LineaPedido> lineas() {
        return lineas; // seguro devolverla directamente
    }
}

En test fixtures

@Test
void filtrar() {
    List<String> entrada = List.of("uno", "dos", "tres", "cuatro");
    List<String> esperado = List.of("dos", "tres");

    List<String> actual = filtrarPorLongitud(entrada, 3, 4);

    assertEquals(esperado, actual);
}

Collectors.toUnmodifiableList/Set/Map (Java 10+)

Para obtener colecciones inmutables directamente desde streams:

List<Integer> pares = numeros.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toUnmodifiableList());

Set<String> nombresUnicos = personas.stream()
.map(Persona::nombre)
.collect(Collectors.toUnmodifiableSet());

Map<String, Integer> indice = productos.stream()
.collect(Collectors.toUnmodifiableMap(Producto::codigo, Producto::stock));

Desde Java 16 existe Stream.toList() que devuelve una lista inmutable por defecto (pero admite null):

List<Integer> pares = numeros.stream()
.filter(n -> n % 2 == 0)
.toList(); // inmutable

Limitaciones

  • Sin null: cualquier null lanza NullPointerException. Si necesitas null, usa ArrayList tradicional.
  • Inmutabilidad superficial: los objetos contenidos sí pueden cambiar. Si quieres inmutabilidad profunda, asegúrate de que los elementos también lo sean.
  • No se puede modificar: en situaciones donde construyes progresivamente, usa ArrayList y al final haz List.copyOf.

Rendimiento

Las implementaciones internas de List.of, Set.of y Map.of están optimizadas:

  • Tamaño 0: constante compartida (una sola instancia en la JVM).
  • Tamaño 1: clase especializada, sin array interno.
  • Tamaño 2: dos campos, sin array.
  • Tamaño N: array compacto.

Para colecciones pequeñas fijas, son más eficientes que ArrayList o HashMap.

Resumen

| Método | Desde | Inmutable | Copia | |--------|-------|-----------|-------| | List.of(...), Set.of(...), Map.of(...) | Java 9 | Sí |: (literal) | | List.copyOf(x), Set.copyOf(x), Map.copyOf(x) | Java 10 | Sí | Sí (copia) | | Collectors.toUnmodifiableList() | Java 10 | Sí |: (de stream) | | Stream.toList() | Java 16 | Sí |: (de stream) | | Collections.unmodifiableList(lista) | Java 1.2 | Vista (no copia) | No | | List.of(...) vs Arrays.asList(...) | 9 vs 1.2 | Inmutable vs tamaño fijo |: |

En código moderno, prefiere colecciones inmutables por defecto. Solo usa colecciones mutables cuando realmente necesites modificarlas. Es un cambio de mentalidad que mejora dramáticamente la robustez de cualquier base de código Java.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Java es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Java

Explora más contenido relacionado con Java y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Crear colecciones inmutables con factorías .of(...) (Java 9+). Copiar colecciones existentes de forma inmutable con .copyOf (Java 10+). Diferenciar colecciones inmutables de vistas no modificables. Comprender que son thread-safe y eficientes en memoria. Manejar las limitaciones: no se pueden modificar (lanzan excepción). Usar Map.entry para mapas con muchas entradas.