CodeGym /Cursos /JAVA 25 SELF /List.of, Set.of, Map.of — colecciones inmutables

List.of, Set.of, Map.of — colecciones inmutables

JAVA 25 SELF
Nivel 34 , Lección 0
Disponible

1. Introducción

Colecciones clásicas: flexibilidad y trampas

Cuando creas una colección con new ArrayList<>(), obtienes una estructura que puedes modificar libremente: añadir, eliminar, cambiar elementos. Es cómodo cuando construyes datos sobre la marcha. Pero, ¿qué pasa si pasas esa colección a otra clase o método donde no debería modificarse? ¿Y si la expones por accidente hacia el exterior y alguien la cambia? Ahí empiezan los problemas.

Ejemplo de error clásico

import java.util.*;

public class Example {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");

        // Pasamos la colección al exterior
        processNames(names);

        // Esperamos que la lista no haya cambiado...
        System.out.println(names);
    }

    public static void processNames(List<String> list) {
        // ¡Y alguien va y elimina un elemento!
        list.remove("Bob");
    }
}

Salida:

[Alice, Charlie]

Tu colección ha cambiado, aunque no lo planeabas en absoluto. En proyectos grandes, estas «sorpresas» se convierten fácilmente en bugs muy desagradables y difíciles de rastrear.

El peligro está en que el código que trabaja con la colección puede encontrarse de repente con cambios impredecibles en los datos. Además, siempre existe el riesgo de pérdida de información: alguien elimina un elemento por accidente o lo sobreescribe. Y si la colección se modifica simultáneamente desde varios hilos, puedes encontrarte no solo con ConcurrentModificationException, sino también con un problema aún más insidioso: datos inconsistentes.

Protección de colecciones: el enfoque antiguo

Antes de Java 9 había que usar métodos envoltorio como Collections.unmodifiableList(...), de los que hablamos en la lección anterior. Ayudan a devolver una colección «congelada». Pero este enfoque no siempre es cómodo y no resuelve todos los problemas (sobre ello, con más detalle, en la próxima lección).

2. Solución moderna: métodos de fábrica List.of, Set.of, Map.of

En Java 9 aparecieron nuevos métodos estáticos en las interfaces de colecciones: List.of, Set.of, Map.of. Permiten crear rápida y cómodamente una colección que no se puede modificar. Es como si crearas la colección y la dejaras fijada en piedra: nadie podrá añadir, eliminar ni cambiar elementos.

Ejemplo de creación de colecciones inmutables

import java.util.*;

public class ImmutableDemo {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Charlie");
        Set<Integer> numbers = Set.of(1, 2, 3);
        Map<String, Integer> ages = Map.of("Alice", 30, "Bob", 25, "Charlie", 28);

        System.out.println(names);
        System.out.println(numbers);
        System.out.println(ages);
    }
}

Salida:

[Alice, Bob, Charlie]
[1, 2, 3]
{Alice=30, Bob=25, Charlie=28}

¿Cómo funciona?

  • List.of(...) — crea una lista inmutable.
  • Set.of(...) — crea un conjunto inmutable.
  • Map.of(...) — crea un mapa inmutable (hasta 10 pares clave-valor; para un número mayor usa Map.ofEntries(...)).

¡Atención! Las colecciones creadas por estos métodos no admiten cambios. Cualquier intento de añadir, eliminar o reemplazar un elemento lanzará una excepción.

3. Ejemplos de uso y «escollos»

Ejemplo: intento de modificar la colección

import java.util.*;

public class ImmutableFail {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob");
        // names.add("Charlie"); // ¡Error en tiempo de ejecución!
        try {
            names.add("Charlie");
        } catch (UnsupportedOperationException ex) {
            System.out.println("No se puede añadir un elemento: " + ex.getClass().getSimpleName());
        }
    }
}

Salida:

No se puede añadir un elemento: UnsupportedOperationException

Ejemplo: intento de añadir null

import java.util.*;

public class NullFail {
    public static void main(String[] args) {
        try {
            List<String> badList = List.of("Alice", null, "Bob");
        } catch (NullPointerException ex) {
            System.out.println("Null no está permitido: " + ex.getClass().getSimpleName());
        }
    }
}

Salida:

Null no está permitido: NullPointerException

Ejemplo: duplicados en Set.of

import java.util.*;

public class DuplicatesFail {
    public static void main(String[] args) {
        try {
            Set<String> badSet = Set.of("one", "two", "one");
        } catch (IllegalArgumentException ex) {
            System.out.println("No se permiten duplicados: " + ex.getClass().getSimpleName());
        }
    }
}

Salida:

No se permiten duplicados: IllegalArgumentException

Ejemplo: Map.of con un gran número de pares

import java.util.*;

public class MapOfLarge {
    public static void main(String[] args) {
        // Map.of admite hasta 10 pares clave-valor
        Map<String, Integer> map = Map.of(
            "one", 1, "two", 2, "three", 3, "four", 4, "five", 5,
            "six", 6, "seven", 7, "eight", 8, "nine", 9, "ten", 10
        );
        System.out.println(map);

        // Para un número mayor usa Map.ofEntries
        Map<String, Integer> bigMap = Map.ofEntries(
            Map.entry("eleven", 11),
            Map.entry("twelve", 12),
            Map.entry("thirteen", 13)
            // ...y así sucesivamente
        );
        System.out.println(bigMap);
    }
}

4. Características y limitaciones de las colecciones inmutables

No se pueden modificar.
Cualquier intento de añadir, eliminar o cambiar un elemento lanzará UnsupportedOperationException. Incluso métodos que normalmente están permitidos (add, remove, set) no funcionan.

No se puede usar null.
Si intentas añadir null como elemento de una lista o conjunto, o como clave/valor en un mapa, obtendrás NullPointerException. Esto se hace por seguridad: los elementos null a menudo provocan errores en las colecciones.

No se garantiza una implementación concreta.
No sabrás qué clase exacta hay bajo el capó de la colección creada mediante List.of y similares. No conviene hacer instanceof ArrayList ni tratar de convertir la colección a un tipo concreto.

Orden de los elementos.
— En List.of se conserva el orden de los elementos (como en una lista normal).
— En Set.of no se garantiza el orden (en la práctica puede coincidir con el orden de los argumentos, pero mejor no confiarse).
— En Map.of no se garantiza el orden de los pares.

Rendimiento.
Las colecciones creadas mediante estos métodos de fábrica suelen funcionar más rápido que los envoltorios sobre colecciones mutables, porque no gastan memoria en capacidades adicionales.

5. Cuándo y para qué usar colecciones inmutables

Conjuntos de datos constantes

Si tienes una lista, conjunto o mapa que no deben cambiar durante la ejecución del programa, usa List.of, Set.of, Map.of. Por ejemplo:

private static final List<String> ROLES = List.of("USER", "ADMIN", "MODERATOR");

Ahora nadie podrá añadir un rol de más a esta lista.

Devolver colecciones desde métodos

Si devuelves una colección desde un método y no quieres que alguien la cambie desde fuera:

public List<String> getDefaultNames() {
    return List.of("Alice", "Bob", "Charlie");
}

Quien la reciba no podrá estropear tus datos.

Paso entre capas de la aplicación

Cuando pasas colecciones entre distintas partes del programa (por ejemplo, entre las capas Controller y Service en una aplicación web), es mejor usar colecciones inmutables para que nadie pueda cambiarlas «a escondidas».

Seguridad y seguridad para hilos

Por definición, las colecciones inmutables son seguras para hilos en lectura: si nadie puede modificarlas, se pueden usar desde varios hilos sin sincronización.

6. Ejemplos prácticos para una aplicación genérica

Supongamos que en nuestra aplicación educativa hay una lista de comandos soportados:

public class Commands {
    public static final List<String> SUPPORTED_COMMANDS = List.of(
        "help", "exit", "list", "add", "remove"
    );
}

Si intentas hacer esto:

Commands.SUPPORTED_COMMANDS.add("hack_the_system");

Obtendrás una excepción y no podrás dañar la aplicación.

O, por ejemplo, si tienes un mapa con códigos de error:

public class ErrorCodes {
    public static final Map<Integer, String> CODES = Map.of(
        404, "Not Found",
        500, "Internal Server Error",
        403, "Forbidden"
    );
}

Cualquier intento de añadir un nuevo código lanzará una excepción.

7. Comparación de enfoques para crear colecciones

Forma de creación ¿Se puede modificar? ¿Se permite null? ¿Duplicados? Seguridad para hilos Ejemplo
new ArrayList<>()
No
new ArrayList<>()
List.of(...)
No No Sí*
List.of("a", "b")
Set.of(...)
No No No Sí*
Set.of("a", "b")
Map.of(...)
No No No Sí*
Map.of("a", 1, "b", 2)
Collections.unmodifiableList(...)
No Depende de la original No
Collections.unmodifiableList(list)

* — seguridad para hilos solo por su inmutabilidad: si nadie modifica la colección, puede leerse de forma segura desde varios hilos.

8. Errores típicos al trabajar con List.of, Set.of, Map.of

Error n.º 1: intento de modificar la colección.
Un error muy frecuente es intentar añadir o eliminar un elemento de una colección creada con List.of, Set.of o Map.of. Por ejemplo, names.add("Dmitry") o ages.remove("Bob"). Esto siempre conduce a UnsupportedOperationException en tiempo de ejecución.

Error n.º 2: intento de añadir null.
Si pasas null por accidente a cualquiera de estos métodos (por ejemplo, List.of("Alice", null)), obtendrás NullPointerException. Las colecciones inmutables de Java 9+ no admiten null — y, en realidad, eso es bueno.

Error n.º 3: duplicados en Set.of o Map.of.
Set.of("a", "b", "a") o Map.of("x", 1, "x", 2) provocarán IllegalArgumentException. Por definición, un conjunto y un mapa no pueden contener duplicados.

Error n.º 4: esperar una implementación concreta.
No conviene hacer esto:

List<String> list = List.of("a", "b");
if (list instanceof ArrayList) { 
    // ...
} // ¡Esto siempre es false!

La implementación interna está oculta — no dependas de los detalles de implementación.

Error n.º 5: intentar usar métodos que modifican la colección.
Incluso métodos como clear(), set(index, value) (en las listas), lanzarán excepciones. Recuerda: las colecciones creadas mediante métodos de fábrica son inmutables.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION