EnumSet and EnumMap in Java: Fast Collections for Enums
EnumSet stores enum constants as bits and EnumMap keeps values in an array by ordinal. Why both beat HashSet and HashMap for enums in Java, and the traps.
- Course: Java study plan
- Module: Enums & records
- Kind: Lesson
- Reading time: 10 min
- Runtime: OpenJDK 18
What are EnumSet and EnumMap in Java?
EnumSet and EnumMap are Java collections built for enum types. An EnumSet stores membership as bits — one long for up to 64 constants — so contains, add and remove are bit operations; an EnumMap stores values in an array indexed by ordinal(). Both are faster and smaller than HashSet and HashMap, iterate in declaration order and reject null keys.
Lesson
A set of days, a map from status to count, a bitmask of permissions — collections keyed by enum constants are everywhere, and Java has two classes built for exactly that shape. EnumSet stores membership as bits; EnumMap stores values in an array indexed by ordinal. Both are faster and smaller than their hash-based cousins, iterate in declaration order, and are what a reviewer expects to see wherever the element or key type is an enum.
EnumSet
EnumSet<Day> weekend = EnumSet.of(Day.SATURDAY, Day.SUNDAY);
EnumSet<Day> weekdays = EnumSet.complementOf(weekend);
EnumSet<Day> all = EnumSet.allOf(Day.class);
EnumSet<Day> none = EnumSet.noneOf(Day.class); // an empty set of Days
EnumSet<Day> midweek = EnumSet.range(Day.TUESDAY, Day.THURSDAY);
EnumSet<Day> copy = EnumSet.copyOf(someCollectionOfDays);
weekend.contains(Day.SUNDAY) // true — a bit test
weekdays.add(Day.SATURDAY); // it is a normal mutable Set
for (Day d : weekdays) … // declaration order: MONDAY … FRIDAY
Inside, an EnumSet of up to 64 constants is a single long; membership, add and remove are bit operations, union and intersection (addAll, retainAll) are | and &. Larger enums use a long[]. It is a full Set<E> — size, removeIf, stream() all work — with iteration in ordinal order. It does not accept null.
Use it for: flag sets (EnumSet<Permission>), "which days" / "which options" selections, and any Set whose element type is an enum. It replaces integer bitmasks (FLAG_A | FLAG_B) with something type-safe and readable — Effective Java item 36.
There is no immutable EnumSet; wrap with Collections.unmodifiableSet or copy defensively.
EnumMap
EnumMap<Status, Integer> counts = new EnumMap<>(Status.class); // the class token is required
for (Order o : orders) counts.merge(o.status(), 1, Integer::sum);
counts.get(Status.ACTIVE) // an array read at index ordinal()
counts.keySet() // in declaration order
counts.getOrDefault(Status.CLOSED, 0)
An EnumMap is an Object[] of size values().length; get and put index by ordinal with no hashing and no boxing of the key. Keys iterate in declaration order. Null keys are rejected; null values are allowed.
Use it for: counts and tallies per constant, lookup tables keyed by enum (EnumMap<Level, Color>), state-machine transition tables (EnumMap<State, EnumSet<State>>). Compared with a HashMap<Status, Integer> it is faster, smaller, ordered, and says in its type that the key set is closed.
Why not ordinal() and an array?
int[] counts = new int[Status.values().length];
counts[status.ordinal()]++; // works — and is exactly what EnumMap does internally
The manual version couples the code to ordinal order and needs a comment; EnumMap says the same thing in the type system with a real Map API. Reach for the raw array only in a measured hot loop.
Enum.valueOf, EnumSet and switch together
A typical flags-style API:
public enum Permission { READ, WRITE, DELETE, ADMIN }
public final class Role {
private final EnumSet<Permission> permissions;
public Role(Collection<Permission> ps) { this.permissions = ps.isEmpty() ? EnumSet.noneOf(Permission.class) : EnumSet.copyOf(ps); }
public boolean can(Permission p) { return permissions.contains(p); }
public Set<Permission> permissions() { return Collections.unmodifiableSet(permissions); }
}
EnumSet.copyOf(collection) throws on an empty non-EnumSet collection (it cannot infer the element type), hence the noneOf branch — a small trap worth knowing.
Streams and enums
Map<Status, Long> tally = orders.stream()
.collect(Collectors.groupingBy(Order::status, () -> new EnumMap<>(Status.class), Collectors.counting()));
groupingBy with a map factory produces an EnumMap; without it you get a HashMap in arbitrary order. Collectors.toCollection(() -> EnumSet.noneOf(Day.class)) does the same for sets.
Performance in one table
| Operation | HashSet/HashMap | EnumSet/EnumMap |
|---|---|---|
| contains / get | hash + equals | one bit test / one array read |
| Memory per element | entry object + boxing | one bit / one array slot |
| Iteration order | unspecified | declaration order |
| Null elements/keys | allowed | rejected |
Interview angle
- "How would you represent a set of flags type-safely?"
EnumSet<Flag>— a bit vector under aSetAPI. - "Why prefer
EnumMaptoHashMapfor enum keys?" Array indexing by ordinal: faster, smaller, ordered. - "What does
EnumSet.copyOf(emptyList)do?" ThrowsIllegalArgumentException— usenoneOf(Class)for empties. - "Is
EnumSetthread-safe?" No, like the other basic collections; wrap or copy.
Key takeaways
EnumSet: bit-vectorSetof enum constants —of,allOf,noneOf,complementOf,range; ordinal-order iteration; no nulls.EnumMap: array-backedMapkeyed by enum — pass the class token; ordered; fast tallies and lookup tables.- Both replace hash collections wherever the element/key is an enum, and replace integer bitmasks and ordinal-indexed arrays.
copyOfof an empty plain collection throws;groupingBytakes a map factory forEnumMap.
Common questions
How do you create an EnumSet in Java?
Through static factories, never new: EnumSet.of(Day.SATURDAY, Day.SUNDAY), EnumSet.allOf(Day.class), EnumSet.noneOf(Day.class) for an empty set, EnumSet.range(from, to), EnumSet.complementOf(set) and EnumSet.copyOf(collection). The result is an ordinary mutable Set.
Why use EnumMap instead of HashMap in Java?
With enum keys an EnumMap is an array read at the key's ordinal — no hashing — so it is faster and smaller, iterates keys in declaration order, and says in its type that the key set is closed. Create it with the class token: new EnumMap<>(Status.class).
Why does EnumSet.copyOf throw on an empty list?
EnumSet.copyOf(collection) needs at least one element to learn the enum type unless the argument is itself an EnumSet, so an empty plain collection throws IllegalArgumentException. Check for empty and use EnumSet.noneOf(Type.class) in that case.
How do you represent a set of flags in Java?
Use an EnumSet of an enum such as Permission. It is a bit vector underneath, like an integer bitmask, but type-safe and readable, with the full Set API — contains, addAll for union and retainAll for intersection. Effective Java recommends it over int flags.
Exercises
Tally by status
Declare enum Status { OPEN, PAID, SHIPPED, CLOSED }. Read an integer n and n status names (exact upper-case names). Count them in an EnumMap<Status, Integer> and print one line per constant in declaration order, <NAME>=<count> (0 for statuses never seen). Then print seen=<the EnumSet of statuses that appeared, via toString> and missing=<EnumSet.complementOf(seen)>.
Example: 3 then PAID OPEN PAID →
OPEN=1
PAID=2
SHIPPED=0
CLOSED=0
seen=[OPEN, PAID]
missing=[SHIPPED, CLOSED]Permission sets
Declare enum Perm { READ, WRITE, EXEC, ADMIN }. Read two lines, each a space-separated list of permissions (or - for none), into two EnumSets a and b. Print union=<…>, intersection=<…>, onlyA=<…> (a minus b) and missingFromA=<EnumSet.complementOf(a)>, each as the set's toString — EnumSets print in declaration order whatever the input order. Then, with an EnumMap<Perm, Integer>, count how many of the two sets contain each permission and print it.
Example: WRITE READ / READ EXEC →
union=[READ, WRITE, EXEC]
intersection=[READ]
onlyA=[WRITE]
missingFromA=[EXEC, ADMIN]
counts={READ=2, WRITE=1, EXEC=1}In this module: Enums & records
← Enums with fields, constructors and behaviour · Records — transparent immutable data →