Watch a Single long Become an EnumSet: A GUI for RegularEnumSet Internals
The bitmask design explains why EnumSet is compact and fast: membership tests are bit reads, mutations are OR and AND-NOT, and the 64-constant cutoff is exactly where one long stops being enough. Seeing the bits move makes that internal tradeoff concrete and makes JVM quirks like --add-opens for reflection visible at the same time.
Java's EnumSet does not use a conventional hash table for small enums. For types with up to 64 constants, RegularEnumSet stores membership in one private long field named elements, using the enum constant's ordinal as a bit index. This GUI demo, built with Swing and generated with Trae, reads that field through reflection and redraws a bit grid after every operation. Users pick one of five enum types, invoke a factory or mutation method, and watch the same state appear in decimal, hex, binary, and per-bit cells.
The left panel also reveals that size is not stored separately; it is computed as Long.bitCount(elements). The operation log records each call, the before and after bitmask, and the exact bit operation: add is elements |= 1L << ordinal, remove is elements &= ~(1L << ordinal), clear zeroes the field. Invalid range calls, where from.ordinal() exceeds to.ordinal(), produce an IllegalArgumentException, a red banner, and a red border around the offending controls. The demo requires --add-opens=java.base/java.util=ALL-UNNAMED on JDK 9+ and uses record syntax, so it needs JDK 16+.
A single long is enough for any enum up to 64 constants, so RegularEnumSet keeps the whole collection in one CPU word; JumboEnumSet exists only because one long stops being enough beyond that.
Logging each operation as a bit expression exposes no-op calls clearly: re-adding an existing constant leaves elements unchanged and the UI does not highlight any bit.
The JDK 9 module system makes reaching this private field a deliberate act: the --add-opens flag is required even for a teaching demo.