1. Introduction
Java has special collections for working with enumerations (enum): EnumSet and EnumMap. They are part of the standard library (java.util) and are designed for maximum efficiency with enum types.
How do EnumSet and EnumMap work under the hood?
EnumSet works like a bitmask. Imagine a set of flags where each enum constant takes exactly one bit. If the bit is on, the element is in the set; if off, it isn’t. Everything is stored in an array of numbers (long[]), and if your enum has fewer than 64 constants, the entire set fits into a single long value!
EnumMap is even simpler: it’s an array of values where the index is the ordinal of the enum element. Instead of a typical HashMap<Enum, V> you get a very compact and fast structure.
What does this give you? Add, remove, and contains operations are O(1). Memory usage is minimal (especially compared to HashSet and HashMap). Iteration order always follows the declaration order in the enum, which makes results predictable.
Example: what it looks like
enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }
EnumSet<Day> weekend = EnumSet.of(Day.SAT, Day.SUN);
System.out.println(weekend); // [SAT, SUN]
From the outside, it’s a regular set. But inside, it’s a number with two bits set: one for SAT and one for SUN. Add FRI — another bit flips on. No hash table and no extra objects.
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}
Here the keys (Day) are turned into internal array indices, so access is as fast as indexing an array.
2. Use cases: flags, tables, finite state machines
EnumSet: ideal for flags and sets of states
- Flags: storing “on/off” sets for a limited number of options.
- Sets of enum values: days of the week, user permissions, task states.
- Finite state machines (FSM): it’s convenient to store allowed transitions in an EnumSet.
Example: access flags
enum Permission { READ, WRITE, EXECUTE }
EnumSet<Permission> perms = EnumSet.of(Permission.READ, Permission.WRITE);
if (perms.contains(Permission.WRITE)) {
// Writing is allowed
}
Example: all values except some
EnumSet<Day> workdays = EnumSet.complementOf(EnumSet.of(Day.SAT, Day.SUN));
System.out.println(workdays); // [MON, TUE, WED, THU, FRI]
EnumMap: ideal for tables keyed by an enum
- Lookup tables: keys are enum values, values are arbitrary objects.
- Fast access: faster and more compact than HashMap<Enum, V>.
Example: prices by day of week
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
Example: finite state machine
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. Pitfalls: changing enums and serialization
EnumSet and EnumMap depend on the set and order of values in your enum. If you add a new element, remove an old one, or reorder them, saved or serialized collections may behave incorrectly.
Serialization
- EnumSet and EnumMap are serializable, but if the enum changes between serialization and deserialization, errors/data loss are possible.
- Removing a value from an enum after serialization will almost certainly cause an exception when reading.
Best practice:
- Don’t serialize EnumSet/EnumMap unless you’re sure the enum is stable.
- For long-term storage use, for example, a list of string representations of the values.
4. Useful details
Comparing EnumSet/EnumMap and regular collections
| Collection | Key/element | Internal structure | Performance | Memory | Null |
|---|---|---|---|---|---|
|
|
bitmask | O(1) | very little | no |
|
|
hash table | O(1) | more | yes |
|
|
array indexed by ordinal | O(1) | very little | no |
|
|
hash table | O(1) | more | yes |
Best practices
- Use EnumSet for sets of values (of, noneOf, allOf, complementOf).
- Use EnumMap for associative tables where the key is an enum.
- Avoid “giant” enums — lists with hundreds of constants reduce compactness.
- Don’t serialize if the enum may change.
- Don’t use null as a key or value.
5. Practice: how to use EnumSet and EnumMap in an application
Example: storing user roles
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);
}
}
Example: state transition table
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. Common mistakes when working with EnumSet/EnumMap
Error #1: Using EnumSet/EnumMap with a non-enum type.
These collections work only with types that are an enum.
// EnumSet<String> set = EnumSet.of("A", "B"); // Compilation error!
Error #2: Using EnumSet/EnumMap for very large enums.
Enums with hundreds of constants reduce compactness. Nevertheless, this is still often more memory-efficient than HashSet/HashMap for the same data.
Error #3: Changing an enum after serialization.
Adding/removing/reordering values after serialization leads to read-time errors or data loss.
Error #4: Using EnumSet/EnumMap with null.
Neither EnumSet elements nor EnumMap keys/values can be null.
EnumSet<Day> days = EnumSet.of(null); // NullPointerException!
EnumMap<Day, String> map = new EnumMap<>(Day.class);
map.put(null, "test"); // NullPointerException!
Error #5: Expecting EnumSet to be a “regular” Set.
EnumSet stores only values of its enum; you cannot add arbitrary objects outside the enumeration.
Error #6: Using EnumSet/EnumMap with “mutable” enums.
If enumerations are generated or swapped at runtime (dynamic loading/reflection), these structures will not work correctly.
GO TO FULL VERSION