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 |
|---|---|---|---|---|---|
|
|
máscara de bits | O(1) | muy poca | no |
|
|
tabla hash | O(1) | más | sí |
|
|
array indexado por ordinal | O(1) | muy poca | no |
|
|
tabla hash | O(1) | más | 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.
GO TO FULL VERSION