CodeGym /Cursos /JAVA 25 SELF /EnumSet/EnumMap

EnumSet/EnumMap

JAVA 25 SELF
Nivel 28 , Lección 3
Disponible

1. Introducción

En Java hay colecciones especiales para trabajar con enumeraciones (enum): EnumSet y EnumMap. Forman parte de la biblioteca estándar (java.util) y están diseñadas para trabajar de forma lo más eficiente posible con tipos enum.

¿Cómo están implementadas por dentro EnumSet y EnumMap?

EnumSet funciona como una máscara de bits. Imagine un conjunto de banderas en el que cada elemento de la enumeración ocupa exactamente un bit. Si el bit está activado — el elemento está en el conjunto; si está desactivado — no lo está. Todo se almacena en un array de números (long[]), y si su enum tiene menos de 64 valores, ¡todo el conjunto cabe en un único número!

EnumMap es aún más simple: es un array de valores donde el índice es el número ordinal (ordinal) del elemento enum. En lugar de un HashMap<Enum, V> habitual, obtiene una estructura muy compacta y rápida.

¿Qué aporta esto? Las operaciones de inserción, eliminación y comprobación se realizan en O(1). Se usa un mínimo de memoria (especialmente en comparación con HashSet y HashMap). La iteración de elementos siempre sigue el orden de declaración en el enum, lo que hace el resultado predecible.

Ejemplo: cómo se ve

enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }

EnumSet<Day> weekend = EnumSet.of(Day.SAT, Day.SUN);
System.out.println(weekend); // [SAT, SUN]

Por fuera es un conjunto normal. Pero por dentro — un número con dos bits a 1: uno para SAT, otro para SUN. Si añade FRI — se activará un bit más. Sin tabla hash ni objetos adicionales.

EnumMap<Day, String> schedule = new EnumMap<>(Day.class);
schedule.put(Day.MON, "Gym");
schedule.put(Day.FRI, "Party");
System.out.println(schedule); // {MON=Gym, FRI=Party}

Aquí las claves (Day) se convierten en índices internos de un array, por lo que el acceso funciona tan rápido como acceder a un elemento de un array.

2. Casos de uso: flags, tablas, autómatas finitos

EnumSet: ideal para flags y conjuntos de estados

  • Flags: almacenar conjuntos «activado/desactivado» para un número limitado de opciones.
  • Conjunto de valores de enum: días de la semana, permisos de usuario, estados de una tarea.
  • Autómatas finitos (FSM): cómodo para guardar las transiciones permitidas en EnumSet.

Ejemplo: flags de acceso

enum Permission { READ, WRITE, EXECUTE }

EnumSet<Permission> perms = EnumSet.of(Permission.READ, Permission.WRITE);
if (perms.contains(Permission.WRITE)) {
    // Se permite escribir
}

Ejemplo: todos los valores excepto algunos

EnumSet<Day> workdays = EnumSet.complementOf(EnumSet.of(Day.SAT, Day.SUN));
System.out.println(workdays); // [MON, TUE, WED, THU, FRI]

EnumMap: ideal para tablas con claves enum

  • Tablas de correspondencias: las claves son valores del enum, y los valores — objetos cualesquiera.
  • Acceso rápido: más veloz y compacto que HashMap<Enum, V>.

Ejemplo: precios por día de la semana

EnumMap<Day, Integer> prices = new EnumMap<>(Day.class);
prices.put(Day.MON, 100);
prices.put(Day.SAT, 200);
System.out.println(prices.get(Day.SAT)); // 200

Ejemplo: autómata finito

enum State { START, RUNNING, STOPPED }
EnumMap<State, EnumSet<State>> transitions = new EnumMap<>(State.class);
transitions.put(State.START, EnumSet.of(State.RUNNING));
transitions.put(State.RUNNING, EnumSet.of(State.STOPPED));
transitions.put(State.STOPPED, EnumSet.noneOf(State.class));

3. Trampas: cambios del enum y serialización

EnumSet y EnumMap dependen de la composición y el orden de los valores de su enum. Si añade un nuevo elemento, borra uno antiguo o cambia su orden, las colecciones guardadas o serializadas pueden comportarse de forma incorrecta.

Serialización

  • EnumSet y EnumMap son serializables, pero si el enum cambia entre la serialización y la deserialización, pueden producirse errores/pérdida de datos.
  • Eliminar un valor del enum después de serializar casi con total seguridad provocará una excepción al leer.

Mejor práctica:

  • No serialice EnumSet/EnumMap si no está seguro de que el enum es estable.
  • Para almacenamiento a largo plazo, utilice, por ejemplo, una lista de representaciones en cadena de los valores.

4. Detalles útiles

Comparación de EnumSet/EnumMap con colecciones habituales

Colección Clave/elemento Estructura interna Rendimiento Memoria Null
EnumSet
enum
máscara de bits O(1) muy poca no
HashSet<Enum>
enum
tabla hash O(1) más
EnumMap
enum
array indexado por ordinal O(1) muy poca no
HashMap<Enum, V>
enum
tabla hash O(1) más

Mejores prácticas

  • Use EnumSet para conjuntos de valores (of, noneOf, allOf, complementOf).
  • Use EnumMap para tablas asociativas, donde la clave — enum.
  • Evite las enumeraciones «gigantes» — listas con cientos de valores reducen la compacidad.
  • No serialice, si el enum puede cambiar.
  • No use null como clave ni como valor.

5. Práctica: cómo usar EnumSet y EnumMap en una aplicación

Ejemplo: almacenamiento de roles de usuario

enum Role { USER, ADMIN, MODERATOR }

class User {
    private EnumSet<Role> roles = EnumSet.noneOf(Role.class);

    public void addRole(Role role) {
        roles.add(role);
    }

    public boolean isAdmin() {
        return roles.contains(Role.ADMIN);
    }
}

Ejemplo: tabla de transiciones de estados

enum State { NEW, IN_PROGRESS, DONE }

EnumMap<State, EnumSet<State>> transitions = new EnumMap<>(State.class);
transitions.put(State.NEW, EnumSet.of(State.IN_PROGRESS));
transitions.put(State.IN_PROGRESS, EnumSet.of(State.DONE));
transitions.put(State.DONE, EnumSet.noneOf(State.class));

6. Errores típicos al trabajar con EnumSet/EnumMap

Error n.º 1: usar EnumSet/EnumMap con un tipo que no es enum.
Estas colecciones solo funcionan con tipos que son enum.

// EnumSet<String> set = EnumSet.of("A", "B"); // ¡Error de compilación!

Error n.º 2: usar EnumSet/EnumMap para enums muy grandes.
Enumeraciones con cientos de valores empeoran la compacidad. Aun así, a menudo siguen siendo mejores en memoria que HashSet/HashMap para los mismos datos.

Error n.º 3: cambiar el enum después de la serialización.
Añadir, eliminar o reordenar valores después de serializar conduce a errores al leer o a pérdida de datos.

Error n.º 4: usar EnumSet/EnumMap con null.
Ni los elementos EnumSet, ni las claves/valores EnumMap pueden ser null.

EnumSet<Day> days = EnumSet.of(null); // NullPointerException!
EnumMap<Day, String> map = new EnumMap<>(Day.class);
map.put(null, "test"); // NullPointerException!

Error n.º 5: esperar que EnumSet sea un Set «normal».
EnumSet solo almacena valores de su propio enum; no puede añadir un objeto arbitrario fuera de la enumeración.

Error n.º 6: usar EnumSet/EnumMap para enums «mutables».
Si las enumeraciones se generan o sustituyen en tiempo de ejecución (carga dinámica/reflexión), estas estructuras no funcionarán correctamente.

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