Contents
- EnumMap — Ordinal-Indexed Map
- EnumSet — Bit-Vector Set
- EnumSet Factory Methods
- Performance Characteristics
- Practical Patterns
EnumMap stores values in an array indexed by each enum constant's ordinal. No hashing, no collisions — lookups and insertions are O(1) with minimal overhead.
import java.util.EnumMap;
enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }
// Create and populate an EnumMap
EnumMap<Day, Integer> workHours = new EnumMap<>(Day.class);
workHours.put(Day.MON, 8);
workHours.put(Day.TUE, 8);
workHours.put(Day.WED, 8);
workHours.put(Day.THU, 8);
workHours.put(Day.FRI, 6);
workHours.put(Day.SAT, 0);
workHours.put(Day.SUN, 0);
System.out.println(workHours.get(Day.FRI)); // 6
// EnumMap iteration is always in enum declaration order (MON → SUN)
workHours.forEach((day, hours) ->
System.out.printf("%s: %d hours%n", day, hours));
// Copy from a regular map
Map<Day, String> dayNames = new HashMap<>();
dayNames.put(Day.MON, "Monday");
dayNames.put(Day.TUE, "Tuesday");
EnumMap<Day, String> enumDayNames = new EnumMap<>(dayNames); // copy constructor
// putIfAbsent, computeIfAbsent — all Map methods work
workHours.putIfAbsent(Day.SAT, 4);
workHours.merge(Day.FRI, 2, Integer::sum); // FRI → 8
// getOrDefault
int hours = workHours.getOrDefault(Day.SUN, 0);
EnumSet represents a set of enum values as a single long bit vector (for enums ≤ 64 values) or a long[] array. Set operations become bitwise operations — extremely fast.
import java.util.EnumSet;
enum Permission { READ, WRITE, EXECUTE, DELETE, ADMIN }
// Creating EnumSets
EnumSet<Permission> empty = EnumSet.noneOf(Permission.class);
EnumSet<Permission> all = EnumSet.allOf(Permission.class);
EnumSet<Permission> readWrite = EnumSet.of(Permission.READ, Permission.WRITE);
EnumSet<Permission> readOnly = EnumSet.of(Permission.READ);
// range — all constants between two (by declaration order)
EnumSet<Permission> firstThree = EnumSet.range(Permission.READ, Permission.EXECUTE);
// {READ, WRITE, EXECUTE}
// complementOf — all constants NOT in the given set
EnumSet<Permission> notReadWrite = EnumSet.complementOf(readWrite);
// {EXECUTE, DELETE, ADMIN}
// copyOf — copy an existing EnumSet
EnumSet<Permission> copy = EnumSet.copyOf(readWrite);
// Standard Set operations — all very fast (bitwise internally)
readWrite.add(Permission.EXECUTE); // add
readWrite.remove(Permission.WRITE); // remove
boolean canRead = readWrite.contains(Permission.READ); // true
// Set algebra
EnumSet<Permission> union = EnumSet.copyOf(readWrite);
union.addAll(readOnly); // union
EnumSet<Permission> intersection = EnumSet.copyOf(readWrite);
intersection.retainAll(readOnly); // intersection
EnumSet<Permission> difference = EnumSet.copyOf(readWrite);
difference.removeAll(readOnly); // difference
EnumSet has no public constructor — instances are always created through static factory methods. The factories cover all common patterns: empty set, full set, explicit members, a contiguous range by declaration order, and the complement of an existing set:
enum Status { PENDING, PROCESSING, SHIPPED, DELIVERED, CANCELLED, RETURNED }
// noneOf — empty set of the given enum type
EnumSet<Status> noStatuses = EnumSet.noneOf(Status.class);
// allOf — set containing all constants
EnumSet<Status> allStatuses = EnumSet.allOf(Status.class);
// of — 1 to 5 args, or varargs (6+ args use the varargs form)
EnumSet<Status> active = EnumSet.of(Status.PENDING, Status.PROCESSING, Status.SHIPPED);
EnumSet<Status> terminal = EnumSet.of(Status.DELIVERED, Status.CANCELLED, Status.RETURNED);
// range — inclusive on both ends, uses enum declaration order
EnumSet<Status> inFlight = EnumSet.range(Status.PROCESSING, Status.SHIPPED);
// {PROCESSING, SHIPPED}
// complementOf — everything else
EnumSet<Status> notActive = EnumSet.complementOf(active);
// {DELIVERED, CANCELLED, RETURNED}
// Using as flags — idiomatic pattern
boolean isTerminal(Status status) {
return terminal.contains(status);
}
boolean isActive(Status status) {
return active.contains(status);
}
// Method accepting Set<Status> — use EnumSet for the argument
void processOrders(EnumSet<Status> statusFilter) {
orders.stream()
.filter(o -> statusFilter.contains(o.status()))
.forEach(this::process);
}
// Call: processOrders(EnumSet.of(Status.PENDING, Status.PROCESSING));
EnumMap is faster than HashMap because it uses an ordinal-indexed array — no hashing, no collision resolution. EnumSet is even more dramatic: for enums with up to 64 constants the entire set is a single long, so operations like contains() reduce to a single bitwise test:
// EnumMap vs HashMap — benchmark illustration (not runnable)
// EnumMap: no hash computation, no collision handling
// HashMap: hash(key) → bucket lookup → possible chain/tree traversal
// EnumSet vs HashSet — even bigger difference
// EnumSet.contains(x): single bit test — O(1), ~1 CPU instruction
// HashSet.contains(x): hash, bucket, equals — O(1) amortized but higher constant
// Memory comparison for an enum with 8 constants, all in the collection:
// EnumSet: 1 long (8 bytes) for the bit vector
// HashSet: 8 Entry objects (~32+ bytes each) + array + header
// EnumSet for flags — replaces int bitmasks cleanly:
// Old (pre-Java-5 style, error-prone):
// static final int READ = 1 << 0;
// static final int WRITE = 1 << 1;
// static final int EXECUTE = 1 << 2;
// int perms = READ | WRITE;
// if ((perms & READ) != 0) { ... }
// Modern (type-safe, readable, same performance):
EnumSet<Permission> perms = EnumSet.of(Permission.READ, Permission.WRITE);
if (perms.contains(Permission.READ)) { /* ... */ }
// Iteration order:
// EnumMap — always enum declaration order (consistent, predictable)
// EnumSet — always enum declaration order
// HashMap — unspecified (insertion or hash order)
// LinkedHashMap — insertion order
// TreeMap — natural/comparator order
Three patterns appear repeatedly in production code: feature-flag configuration (EnumMap<Feature, Boolean>), strategy dispatch (EnumMap<Op, Function> replacing switch statements), and role-based access control (EnumMap<Role, EnumSet<Permission>>):
// Pattern 1: Configuration with EnumMap
enum Feature { DARK_MODE, NOTIFICATIONS, AUTO_SAVE, SPELL_CHECK }
class UserPreferences {
private final EnumMap<Feature, Boolean> enabled =
new EnumMap<>(Feature.class);
public UserPreferences() {
// Default all features to false
for (Feature f : Feature.values()) enabled.put(f, false);
}
public void enable(Feature feature) { enabled.put(feature, true); }
public void disable(Feature feature) { enabled.put(feature, false); }
public boolean isEnabled(Feature f) { return enabled.getOrDefault(f, false); }
public EnumSet<Feature> enabledFeatures() {
return enabled.entrySet().stream()
.filter(Map.Entry::getValue)
.map(Map.Entry::getKey)
.collect(Collectors.toCollection(() -> EnumSet.noneOf(Feature.class)));
}
}
// Pattern 2: Strategy dispatch with EnumMap
enum Operation { ADD, SUBTRACT, MULTIPLY, DIVIDE }
EnumMap<Operation, IntBinaryOperator> ops = new EnumMap<>(Operation.class);
ops.put(Operation.ADD, (a, b) -> a + b);
ops.put(Operation.SUBTRACT, (a, b) -> a - b);
ops.put(Operation.MULTIPLY, (a, b) -> a * b);
ops.put(Operation.DIVIDE, (a, b) -> a / b);
int calculate(Operation op, int a, int b) {
return ops.get(op).applyAsInt(a, b);
}
// Pattern 3: Role-based access control with EnumSet
enum Role { USER, MODERATOR, ADMIN }
EnumMap<Role, EnumSet<Permission>> rolePermissions = new EnumMap<>(Role.class);
rolePermissions.put(Role.USER, EnumSet.of(Permission.READ));
rolePermissions.put(Role.MODERATOR, EnumSet.of(Permission.READ, Permission.WRITE, Permission.DELETE));
rolePermissions.put(Role.ADMIN, EnumSet.allOf(Permission.class));
boolean hasPermission(Role role, Permission perm) {
return rolePermissions.getOrDefault(role, EnumSet.noneOf(Permission.class))
.contains(perm);
}
Always use EnumMap and EnumSet when keys/elements are enum constants. They're never the wrong choice — they are type-safe, faster, and more memory-efficient than HashMap and HashSet for this use case.