跪拜 Guibai
← Back to the summary

A CLI That Shows EnumMap's Internal State: Ordinal Index, NULL Sentinel, and Eager Allocation

Background

In previous articles, we used GUI and CLI to observe java.util.EnumSet

In EnumSet, ordinal is used to locate a bit. java.util.EnumMap follows the same idea but uses it more directly—ordinal is the array index.

In the source code of EnumMap, the following fields can be seen:

image.png

The five observable facts corresponding to these fields:

The constructor logic is fairly straightforward:

image.png

In the constructor, space is allocated for vals.

Comparing with EnumSet, the two differ in how they use ordinal:

bitmap index
EnumSet ordinal locates a bit in long/long[] —
EnumMap — ordinal directly used as the index into the vals array

We can observe how keyUniverse, vals, and size change during key-value pair insertion and deletion in java.util.EnumMap using reflection.

The article [Java] 用 GUI 展示 EnumMap 增删键值对时的内部变化 used grid cells in a square diagram to correspond to elements of vals, with different colors distinguishing “empty slot / real value / NULL sentinel”.

This article switches to CLI. For EnumMap, that choice is natural:

Notes

Main Text

I used trae to generate the CLI code that can observe the internal state of java.util.EnumMap. For the sake of coherent writing, I placed the complete code in the Complete Code section below.

How to Run

Save the Complete Code as EnumMapCliDemo.java. Compile it with the following command:

javac EnumMapCliDemo.java

After compilation, you will get the following class files

Use the following command to run the main method in EnumMapCliDemo:

java --add-opens=java.base/java.util=ALL-UNNAMED EnumMapCliDemo

After starting, an interactive loop appears. Enter a command and press Enter. You can also pipe commands in to reproduce the same output:

printf 'new Vowel\nput A alpha\nexit\n' | java --add-opens=java.base/java.util=ALL-UNNAMED EnumMapCliDemo

Command Overview

Enter help to see all commands at any time:

commands:
  new <enum>          create the map; <enum> = Planet|Vowel|Digit|Month|Direction
  put <key> <value>   put a mapping; value `null` maps to null
  get <key>           get(key)
  containsKey <key>   containsKey(key)
  remove <key>        remove(key)
  clear               clear()
  show                print keyType / keyUniverse / vals / size
  reset               drop the map and start over
  help                this text
  exit                quit
note: values are always String; `null` is a literal, not a string.
slot display:
  null                           no mapping (vals[i] == null)
  NULL sentinel (mapped to null) the key is mapped to null (maskNull)
  "value"                        a real value

Commands fall into three categories:

The enum name in new and the key in put are case-insensitive; new vowel, put u null are also recognized. Values can contain spaces (put MARS red giant); only a standalone null is treated as the null literal, not the string "null".

In my terminal, Chinese and English mixed together cause misalignment, so in this article I deliberately used only English-only enums:

Initial Output

At startup, the banner explains that there is no map yet and shows the three slot display forms:

EnumMap CLI demo (java.util.EnumMap internals)
no map yet - run: new <enum>
type help for all commands
slot display: null = no mapping, NULL sentinel (mapped to null) = key mapped to null, "v" = a real value

Before new, commands put / get / containsKey / remove / clear / show all prompt you to create a map first:

> put A alpha
no map yet: run new <enum> first

Calling the Constructor: new

Entering new Vowel executes logic like new EnumMap<>(Vowel.class) and outputs the following:

new EnumMap<>(Vowel.class)
  EnumMap<Vowel, String>   keyType = Vowel.class
  keyUniverse.length = 5   vals.length = 5   size = 0
    [0] A -> null
    [1] E -> null
    [2] I -> null
    [3] O -> null
    [4] U -> null

The output can be divided into two layers:

Several points worth noting:

This step also reflects the role of keyType: EnumMap must know which enum type it is storing in order to get keyUniverse and thus determine the length of vals.

Adding Key-Value Pairs: put(key, value)

Entering put A alpha outputs the following:

put(Vowel.A, "alpha")
  vals[0]: null -> "alpha"   size: 0 -> 1
  EnumMap<Vowel, String>   keyType = Vowel.class
  keyUniverse.length = 5   vals.length = 5   size = 1
    [0] A -> "alpha"
    [1] E -> null
    [2] I -> null
    [3] O -> null
    [4] U -> null

The result is in three sections:

  1. The first line is the call expression put(Vowel.A, "alpha")
  2. The second line indicates the touched slot and its change: vals[0]: null -> "alpha", and the change in size
  3. After that is the refreshed complete state

Since A's ordinal is 0, the value is written directly into vals[0], and size goes from 0 to 1. The whole process involves no complex logic like "perturbation + locating + linked list" found in HashMap.

Running put again on the same key overwrites the value, but size stays the same. For example, entering put A another gives:

put(Vowel.A, "another")
  vals[0]: "alpha" -> "another"   size unchanged (1)
  EnumMap<Vowel, String>   keyType = Vowel.class
  keyUniverse.length = 5   vals.length = 5   size = 1
    [0] A -> "another"
    [1] E -> null
    [2] I -> null
    [3] O -> null
    [4] U -> null

This corresponds to the put method in the JDK source:

image.png

Only when the original slot is null does size increase by 1. The two notations size: 0 -> 1 and size unchanged (1) directly reflect this logic.

Adding Key-Value Pairs: put(key, null)

Entering put U null gives:

put(Vowel.U, null)
  vals[4]: null -> NULL sentinel (mapped to null)   size: 1 -> 2
  note: maskNull stored the NULL sentinel; containsKey stays true
  EnumMap<Vowel, String>   keyType = Vowel.class
  keyUniverse.length = 5   vals.length = 5   size = 2
    [0] A -> "another"
    [1] E -> null
    [2] I -> null
    [3] O -> null
    [4] U -> NULL sentinel (mapped to null)

This is a confusing detail of EnumMap: put(key, null) does not store null, but rather the private NULL sentinel object. The maskNull / unmaskNull methods handle this conversion:

image.png

The reason for this distinction is that vals[i] == null must mean "this key has no mapping", while "mapped to null" needs a distinguishable marker. Otherwise, containsKey could not be handled correctly. This is somewhat like the difference between "I have an empty wallet" and "I don't even have a wallet".

So the demo displays slots in three forms:

The text NULL sentinel (mapped to null) is deliberately longer than a plain NULL, so readers immediately know this is not null but a sentinel, and that it represents "there is a mapping, with a value of null".

get and containsKey

Executing the get U command produces:

get(Vowel.U) -> null   (unmaskNull(NULL): looks absent, but containsKey is true)

Executing the containsKey U command produces:

containsKey(Vowel.U) -> true   (vals[4] != null: present, even if it is the NULL sentinel)

The results appear contradictory but are consistent:

Doing the same for a key that was never stored (using E as an example):

> get E
get(Vowel.E) -> null   (vals[1] == null: no mapping)
> containsKey E
containsKey(Vowel.E) -> false   (vals[1] == null: no mapping)

Now it is "truly no such key": the slot itself is null, and both methods give the "does not exist" conclusion.

The explanatory text in parentheses directly shows the criterion, making it easy to match the output with the source code: both U and E return null for get, but only U's slot is a sentinel, so containsKey differs.

Deleting Key-Value Pairs: remove

Entering remove U removes the key-value pair that maps to null:

> remove U
remove(Vowel.U) -> null
  vals[4]: NULL sentinel (mapped to null) -> null   size: 2 -> 1
  note: the slot held the NULL sentinel, so remove() returned null (unmaskNull)
  EnumMap<Vowel, String>   keyType = Vowel.class
  keyUniverse.length = 5   vals.length = 5   size = 1
    [0] A -> "another"
    [1] E -> null
    [2] I -> null
    [3] O -> null
    [4] U -> null

The return value of remove also goes through unmaskNull, so it looks like null, the same as when the key does not exist. The only way to tell is whether size changed. Here size goes from 2 to 1, indicating a mapping was indeed deleted.

If you run remove on a key that has no mapping, nothing changes:

> remove I
remove(Vowel.I) -> null
  vals[2] was already null: no-op, size unchanged (1)
  EnumMap<Vowel, String>   keyType = Vowel.class
  keyUniverse.length = 5   vals.length = 5   size = 1
    [0] A -> "another"
    [1] E -> null
    [2] I -> null
    [3] O -> null
    [4] U -> null

Notice that this line has no -> and no vals[2]: ... -> ... change line, only a no-op note.

clear()

Entering clear empties all slots:

> clear
clear()   Arrays.fill(vals, null); size: 1 -> 0
  EnumMap<Vowel, String>   keyType = Vowel.class
  keyUniverse.length = 5   vals.length = 5   size = 0
    [0] A -> null
    [1] E -> null
    [2] I -> null
    [3] O -> null
    [4] U -> null

The source corresponds to Arrays.fill(vals, null) plus size = 0. Note that the length of vals does not change—clear only fills the slots with null; the array itself is kept, and the next put does not need reallocation.

image.png

Switching Key Enum Type

new Month starts over with a different enum, this time with 12 constants:

new EnumMap<>(Month.class)
  EnumMap<Month, String>   keyType = Month.class
  keyUniverse.length = 12   vals.length = 12   size = 0
    [ 0] JANUARY   -> null
    [ 1] FEBRUARY  -> null
    [ 2] MARCH     -> null
    [ 3] APRIL     -> null
    [ 4] MAY       -> null
    [ 5] JUNE      -> null
    [ 6] JULY      -> null
    [ 7] AUGUST    -> null
    [ 8] SEPTEMBER -> null
    [ 9] OCTOBER   -> null
    [10] NOVEMBER  -> null
    [11] DECEMBER  -> null

Two additional alignment details appear here, both unique to the CLI format:

The padding uses String.repeat, implemented as follows:

private static String pad(String s, int width) {
    return s + " ".repeat(width - s.length());
}

The space in [ 0] is only alignment padding, not part of the data.

show and reset

show is a read-only command that reprints the current state at any time without changing the map:

  EnumMap<Month, String>   keyType = Month.class
  keyUniverse.length = 12   vals.length = 12   size = 0
    [ 0] JANUARY   -> null
    [ 1] FEBRUARY  -> null
    [ 2] MARCH     -> null
    [ 3] APRIL     -> null
    [ 4] MAY       -> null
    [ 5] JUNE      -> null
    [ 6] JULY      -> null
    [ 7] AUGUST    -> null
    [ 8] SEPTEMBER -> null
    [ 9] OCTOBER   -> null
    [10] NOVEMBER  -> null
    [11] DECEMBER  -> null

Every put / remove / clear also prints the same content automatically, so show is mainly used when the previous command's output has been scrolled away.

reset corresponds to the Reset button in the GUI article and does not correspond to any Java API:

> reset
map dropped (keyType, keyUniverse, vals, size are gone): run new <enum>

It sets the map to null, so keyType, keyUniverse, vals, size lose their references, and the state returns to the initial condition. At this point, show only prompts that there is no map, rather than raising an error:

> show
no map yet: run new <enum> first

Complete Code

import java.lang.reflect.Field;
import java.util.EnumMap;
import java.util.List;
import java.util.Scanner;

/**
 * Pure CLI counterpart of EnumMapBehaviorDemo: it observes the internal
 * state of java.util.EnumMap after each operation.
 * <p>
 * EnumMap keeps two parallel arrays: "keyUniverse" holds every constant of
 * the key enum, and "vals" holds the associated values. A key's ordinal is
 * used DIRECTLY as the array index - there is no hashing at all. The enum
 * class itself is stored in the final field "keyType" at construction and
 * never changes afterward. A private static sentinel object NULL stands in
 * for null values, so "no mapping" (slot == null) and "mapped to null"
 * (slot == NULL) stay distinguishable.
 * <p>
 * How the demo prints a slot:
 *   null                          - no mapping (vals[i] == null);
 *   NULL sentinel (mapped to null) - the key is mapped to null (the private
 *                                    NULL object is stored via maskNull);
 *   "value"                       - a real value.
 * <p>
 * Observable facts:
 * 1) new EnumMap<>(X.class) -> keyType = X.class (final), keyUniverse = all
 *    constants of X, vals = new Object[n], all null, size = 0 (allocated
 *    eagerly: no lazy init like ArrayList's empty-arg constructor);
 * 2) put(k, v) -> vals[k.ordinal()] = v (ordinal IS the index: no hashing);
 * 3) put(k, null) -> vals[k.ordinal()] = NULL (maskNull: the sentinel keeps
 *    containsKey true even though get(k) returns null);
 * 4) remove(k) -> vals[k.ordinal()] = null;
 * 5) clear() -> Arrays.fill(vals, null), and size = 0;
 * 6) size is a maintained field, not a recomputation.
 * <p>
 * The fields are private, so reflection is required. On JDK 9+ run with:
 * javac EnumMapCliDemo.java
 * java --add-opens=java.base/java.util=ALL-UNNAMED EnumMapCliDemo
 */
public class EnumMapCliDemo {

    // ---- selectable key enums ----

    /** The eight planets. */
    private enum Planet { MERCURY, VENUS, EARTH, MARS, JUPITER, SATURN, URANUS, NEPTUNE }

    /** The five vowels. */
    private enum Vowel { A, E, I, O, U }

    /** The ten digits. */
    private enum Digit { ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE }

    /** The twelve months. */
    private enum Month {
        JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE,
        JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER
    }

    /** The four cardinal directions. */
    private enum Direction { EAST, SOUTH, WEST, NORTH }

    private record EnumType(String displayName, Class<? extends Enum<?>> type) { }

    private static final List<EnumType> TYPES = List.of(
            new EnumType("Planet", Planet.class),
            new EnumType("Vowel", Vowel.class),
            new EnumType("Digit", Digit.class),
            new EnumType("Month", Month.class),
            new EnumType("Direction", Direction.class));

    // ---- reflection on java.util.EnumMap ----
    private static Field keyTypeField;
    private static Field keyUniverseField;
    private static Field valsField;
    private static Field sizeField;
    private static Field nullField;

    // ---- state ----
    /** null means "no map yet": operations are rejected until new <enum> runs. */
    private EnumMap<?, String> map;

    public static void main(String[] args) {
        try {
            initReflection();
        } catch (ReflectiveOperationException | RuntimeException e) {
            System.out.println("cannot read java.util.EnumMap private fields: " + e);
            System.out.println("on JDK 9+ run with: "
                    + "java --add-opens=java.base/java.util=ALL-UNNAMED EnumMapCliDemo");
            return;
        }
        new EnumMapCliDemo().run();
    }

    private static void initReflection() throws ReflectiveOperationException {
        keyTypeField = accessible("keyType");
        keyUniverseField = accessible("keyUniverse");
        valsField = accessible("vals");
        sizeField = accessible("size");
        nullField = accessible("NULL");
    }

    private static Field accessible(String name) throws ReflectiveOperationException {
        Field f = EnumMap.class.getDeclaredField(name);
        f.setAccessible(true);
        return f;
    }

    // ---- command loop ----

    private void run() {
        printBanner();
        Scanner in = new Scanner(System.in);
        while (true) {
            System.out.print("> ");
            System.out.flush();
            if (!in.hasNextLine()) {
                break;
            }
            String line = in.nextLine().trim();
            if (line.isEmpty()) {
                continue;
            }
            String[] parts = line.split("\\s+", 2);
            String command = parts[0].toLowerCase();
            String rest = parts.length > 1 ? parts[1].trim() : "";
            switch (command) {
                case "help", "h", "?" -> printHelp();
                case "new" -> doNew(rest);
                case "put" -> doPut(rest);
                case "get" -> doGet(rest);
                case "containskey", "contains" -> doContainsKey(rest);
                case "remove" -> doRemove(rest);
                case "clear" -> doClear();
                case "show", "s" -> show();
                case "reset" -> doReset();
                case "exit", "quit" -> {
                    System.out.println("bye");
                    return;
                }
                default -> System.out.println("unknown command: " + parts[0] + " (type help)");
            }
        }
    }

    private void printBanner() {
        System.out.println("""
                EnumMap CLI demo (java.util.EnumMap internals)
                no map yet - run: new <enum>
                type help for all commands
                slot display: null = no mapping, NULL sentinel (mapped to null) = key mapped to null, "v" = a real value
                """);
    }

    private void printHelp() {
        System.out.println("""
                commands:
                  new <enum>          create the map; <enum> = Planet|Vowel|Digit|Month|Direction
                  put <key> <value>   put a mapping; value `null` maps to null
                  get <key>           get(key)
                  containsKey <key>   containsKey(key)
                  remove <key>        remove(key)
                  clear               clear()
                  show                print keyType / keyUniverse / vals / size
                  reset               drop the map and start over
                  help                this text
                  exit                quit
                note: values are always String; `null` is a literal, not a string.
                slot display:
                  null                           no mapping (vals[i] == null)
                  NULL sentinel (mapped to null) the key is mapped to null (maskNull)
                  "value"                        a real value""");
    }

    // ---- operations ----

    private void doNew(String arg) {
        EnumType t = findType(arg);
        if (t == null) {
            System.out.println("unknown enum type: " + arg + " (valid: Planet, Vowel, Digit, Month, Direction)");
            return;
        }
        map = newMap(t.type());
        System.out.printf("new EnumMap<>(%s.class)%n", t.displayName());
        show();
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private EnumMap<?, String> newMap(Class<? extends Enum<?>> type) {
        return new EnumMap(type);
    }

    private void doPut(String rest) {
        if (!requireMap()) {
            return;
        }
        String[] kv = rest.split("\\s+", 2);
        if (kv.length < 2) {
            System.out.println("usage: put <key> <value>   (use `null` for a null value)");
            return;
        }
        Enum<?> key = findKey(kv[0]);
        if (key == null) {
            return;
        }
        String raw = kv[1].trim();
        String value = "null".equalsIgnoreCase(raw) ? null : stripQuotes(raw);

        int index = key.ordinal();
        Object before = vals()[index];
        int sizeBefore = size();
        putRaw(key, value);
        Object after = vals()[index];
        int sizeAfter = size();

        System.out.printf("put(%s, %s)%n", qualified(key), quoted(value));
        String sizeText = before == null
                ? String.format("size: %d -> %d", sizeBefore, sizeAfter)
                : String.format("size unchanged (%d)", sizeAfter);
        System.out.printf("  vals[%d]: %s -> %s   %s%n",
                index, slotText(before), slotText(after), sizeText);
        if (after == nullSentinel()) {
            System.out.println("  note: maskNull stored the NULL sentinel; containsKey stays true");
        }
        show();
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private void putRaw(Enum<?> key, String value) {
        ((EnumMap) map).put(key, value);
    }

    private void doGet(String rest) {
        if (!requireMap()) {
            return;
        }
        Enum<?> key = findKey(rest);
        if (key == null) {
            return;
        }
        Object slot = vals()[key.ordinal()];
        String result = map.get(key);
        System.out.printf("get(%s) -> %s", qualified(key), quoted(result));
        if (slot == null) {
            System.out.print("   (vals[" + key.ordinal() + "] == null: no mapping)");
        } else if (slot == nullSentinel()) {
            System.out.print("   (unmaskNull(NULL): looks absent, but containsKey is true)");
        }
        System.out.println();
    }

    private void doContainsKey(String rest) {
        if (!requireMap()) {
            return;
        }
        Enum<?> key = findKey(rest);
        if (key == null) {
            return;
        }
        int index = key.ordinal();
        Object slot = vals()[index];
        boolean present = map.containsKey(key);
        String why = slot == null
                ? "vals[" + index + "] == null: no mapping"
                : "vals[" + index + "] != null: present, even if it is the NULL sentinel";
        System.out.printf("containsKey(%s) -> %s   (%s)%n", qualified(key), present, why);
    }

    private void doRemove(String rest) {
        if (!requireMap()) {
            return;
        }
        Enum<?> key = findKey(rest);
        if (key == null) {
            return;
        }
        int index = key.ordinal();
        Object before = vals()[index];
        int sizeBefore = size();
        String removed = map.remove(key);
        int sizeAfter = size();

        System.out.printf("remove(%s) -> %s%n", qualified(key), quoted(removed));
        if (before == null) {
            System.out.printf("  vals[%d] was already null: no-op, size unchanged (%d)%n", index, sizeAfter);
        } else {
            System.out.printf("  vals[%d]: %s -> null   size: %d -> %d%n",
                    index, slotText(before), sizeBefore, sizeAfter);
            if (before == nullSentinel()) {
                System.out.println("  note: the slot held the NULL sentinel, so remove() returned null (unmaskNull)");
            }
        }
        show();
    }

    private void doClear() {
        if (!requireMap()) {
            return;
        }
        int sizeBefore = size();
        map.clear();
        System.out.printf("clear()   Arrays.fill(vals, null); size: %d -> %d%n", sizeBefore, size());
        show();
    }

    private void doReset() {
        map = null;
        System.out.println("map dropped (keyType, keyUniverse, vals, size are gone): run new <enum>");
    }

    // ---- state output ----

    private void show() {
        if (map == null) {
            System.out.println("no map yet: run new <enum> first");
            return;
        }
        Object[] universe = keyUniverse();
        Object[] values = vals();
        int keyWidth = 0;
        for (Object constant : universe) {
            keyWidth = Math.max(keyWidth, ((Enum<?>) constant).name().length());
        }
        int indexWidth = String.valueOf(values.length - 1).length();
        System.out.printf("  EnumMap<%s, String>   keyType = %s.class%n", typeName(), typeName());
        System.out.printf("  keyUniverse.length = %d   vals.length = %d   size = %d%n",
                universe.length, values.length, size());
        for (int i = 0; i < values.length; i++) {
            System.out.printf("    [%" + indexWidth + "d] %s -> %s%n",
                    i, pad(((Enum<?>) universe[i]).name(), keyWidth), slotText(values[i]));
        }
        System.out.println();
    }

    /** null = no mapping, NULL sentinel = mapped to null, otherwise the quoted value. */
    private String slotText(Object slot) {
        if (slot == null) {
            return "null";
        }
        if (slot == nullSentinel()) {
            return "NULL sentinel (mapped to null)";
        }
        return "\"" + slot + "\"";
    }

    // ---- reflection accessors ----

    private Object[] keyUniverse() {
        return readArray(keyUniverseField);
    }

    private Object[] vals() {
        return readArray(valsField);
    }

    private Object[] readArray(Field field) {
        try {
            return (Object[]) field.get(map);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
    }

    private Class<?> keyType() {
        try {
            return (Class<?>) keyTypeField.get(map);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
    }

    private int size() {
        try {
            return sizeField.getInt(map);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
    }

    private Object nullSentinel() {
        try {
            return nullField.get(null);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(e);
        }
    }

    private String typeName() {
        return keyType().getSimpleName();
    }

    // ---- helpers ----

    private boolean requireMap() {
        if (map == null) {
            System.out.println("no map yet: run new <enum> first");
            return false;
        }
        return true;
    }

    private EnumType findType(String arg) {
        for (EnumType t : TYPES) {
            if (t.displayName().equalsIgnoreCase(arg) || t.displayName().equals(arg)) {
                return t;
            }
        }
        return null;
    }

    private Enum<?> findKey(String name) {
        if (map == null) {
            return null;
        }
        for (Object constant : keyUniverse()) {
            Enum<?> e = (Enum<?>) constant;
            if (e.name().equals(name) || e.name().equalsIgnoreCase(name)) {
                return e;
            }
        }
        System.out.println("unknown key: " + name + " (valid: " + keyNames() + ")");
        return null;
    }

    private String keyNames() {
        StringBuilder builder = new StringBuilder();
        for (Object constant : keyUniverse()) {
            if (!builder.isEmpty()) {
                builder.append(' ');
            }
            builder.append(((Enum<?>) constant).name());
        }
        return builder.toString();
    }

    private String qualified(Enum<?> key) {
        return typeName() + "." + key.name();
    }

    private static String quoted(String value) {
        return value == null ? "null" : "\"" + value + "\"";
    }

    private static String stripQuotes(String value) {
        if (value.length() >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
            return value.substring(1, value.length() - 1);
        }
        return value;
    }

    /** Right-pads with spaces so that keys line up in the slot listing. */
    private static String pad(String s, int width) {
        return s + " ".repeat(width - s.length());
    }
}

References

Comments

Top 1 of 2 from juejin.cn, machine-translated. The original thread is authoritative.

卷土的土

The sentinel distinguishes null values from missing keys.

金銀銅鐵

Yes, that's right.