EnumMap Internals, Visualized: The Ordinal-Indexed Array and NULL Sentinel
The NULL sentinel is why a key mapped to null still shows up in containsKey() but not in get(); without that distinction, code that checks existence after a null get() would silently misbehave. Understanding EnumMap's eager array allocation and ordinal indexing also makes the performance tradeoff against HashMap explicit.
EnumMap is one of the least hash-like maps in the Java collections framework: a key's ordinal is the array index, so lookups skip hashing, modulo, and collision handling entirely. A new Swing demo now makes that internal layout visible by reading EnumMap's private fields through reflection and drawing one cell per vals slot. The demo also exposes two behaviors that often surprise developers: put(key, null) stores a private static NULL sentinel rather than null, which is why get() can return null while containsKey(key) returns true; and remove() and clear() write null back into the slots without shrinking the array. The visual grid turns gray for absent keys, blue for real values, and orange for the NULL sentinel, with an amber border marking the slot touched by the last operation. The full Java source runs on JDK 16+ and requires --add-opens=java.base/java.util=ALL-UNNAMED to bypass the module system when reflecting into EnumMap's private fields.
The eager allocation of vals means EnumMap's memory footprint is fixed at construction time, proportional to the enum constant count; for enums with many constants or many short-lived maps, that can be wasteful compared to HashMap's lazy table growth.
The NULL sentinel pattern is a clean way to support null values in a structure that otherwise uses null to signal absence; any developer relying on EnumMap for nullable values needs to remember that get() and containsKey() answer different questions.
Reading private fields with reflection is fine for an educational demo, but the module system's --add-opens requirement is a reminder that these internals are not a stable API and could change in future JDK versions.
You could add a comparison of the state after removing a null-value mapping.
Okay, adjusted.