跪拜 Guibai
← Back to the summary

Watch a Single long Become an EnumSet: A GUI for RegularEnumSet Internals

Background

In daily development, you have probably used java.util.EnumSet. In the static method java.util.EnumSet#noneOf, the following logic can be seen:

image.png

The two classes below are both subclasses of EnumSet:

The exact type returned by java.util.EnumSet#noneOf falls into two cases:

In java.util.RegularEnumSet, a field named elements can be found:

image.png

By using reflection, we can observe how the elements field changes when elements are added to and removed from java.util.RegularEnumSet.

Notes

The complete code uses record syntax, so it requires JDK 16 or higher (JDK 21 was used when writing this article).

image.png

Body

I used trae to generate code that can observe changes to the elements field of java.util.RegularEnumSet. For readability, the complete code is placed in the Complete code section.

How to run

Save the Complete code as RegularEnumSetBehaviorDemo.java. Use the following command to compile it:

javac RegularEnumSetBehaviorDemo.java

The Complete code contains Chinese identifiers such as 五行, 天干. If you encounter encoding problems, you can try this command:

javac -encoding UTF-8 RegularEnumSetBehaviorDemo.java

After compilation, you will get the following class files:

Use the following command to run the main method in RegularEnumSetBehaviorDemo. Because reading the private elements field of RegularEnumSet through reflection is restricted by the module system on JDK 9+, the --add-opens option is required:

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

Example output

Initial effect

image.png

internal state of the EnumSet panel

The internal state of the EnumSet panel on the left shows the current value of the elements field and refreshes after each operation. Using EnumSet.noneOf(五行.class) as an example, the panel displays 5 lines of information:

Correspondence between bits and ordinals

elements is a bitmask: bit i is 1 if and only if the enum constant with ordinal i belongs to the set. Taking 五行 as an example, 金 has ordinal 0, so it corresponds to the lowest bit (bit 0), and 土 has ordinal 4, so it corresponds to bit 4.

The grid below the panel is drawn according to this rule: from right to left are bit 0, bit 1, ..., and each cell shows the bit i corresponding to that constant and its current value (1 means in the set, 0 means not in the set). Before a factory method is called, the set does not exist yet, so the cell values show ?.

Therefore EnumSet.allOf(五行.class) produces elements equal to 31 (0b11111, the low 5 bits are all 1), while EnumSet.range(五行.金, 五行.水) produces elements equal to 7 (0b111, bit 0 to bit 2 are 1).

In addition, the bit that most recently changed the state is outlined in orange in the grid. If an operation does not change any bit, such as repeatedly adding an existing element, no highlight appears.

Choose an enum type to operate on

There are 5 enum types to choose from:

image.png

Their Java code is as follows (for readability, the Chinese culture related enums 五行, 天干, 地支, 方位 use Chinese characters directly, although this does not conform to everyday Java naming conventions):

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

/** 金木水火土: metal, wood, water, fire, earth. */
private enum 五行 { 金, 木, 水, 火, 土 }

/** 甲乙丙丁戊己庚辛壬癸: the ten heavenly stems. */
private enum 天干 { 甲, 乙, 丙, 丁, 戊, 己, 庚, 辛, 壬, 癸 }

/** 子丑寅卯辰巳午未申酉戌亥: the twelve earthly branches. */
private enum 地支 { 子, 丑, 寅, 卯, 辰, 巳, 午, 未, 申, 酉, 戌, 亥 }

/** 东西南北: east, south, west, north. */
private enum 方位 { 东, 南, 西, 北 }

Call factory methods

Using 五行 as an example, the following factory methods in EnumSet can be called:

The buttons for the three static factory methods are marked with red frames in the image below:

image.png

When the range arguments are invalid

EnumSet.range(from, to) requires from.ordinal() <= to.ordinal(), otherwise it throws IllegalArgumentException.

For example, executing EnumSet.range(五行.土, 五行.金) produces feedback in three places:

image.png

In the image above, the red arrows point to the range button that triggered the error, the red banner above, and the exception record in the Operation log.

The error state is cleared after a successful operation, switching the enum type, or clicking the Reset button.

Adding elements

Using 五行 as an example, select one element in 五行 and execute the corresponding add method:

image.png

The change in the elements field can be seen at this point.

The add method is defined in the java.util.Collection interface; the actual method called is the add method in java.util.RegularEnumSet:

image.png

Removing elements

Using 五行 as an example, select one element in 五行 and execute the corresponding remove method:

image.png

The change in the elements field can be seen at this point.

The remove method is defined in the java.util.Collection interface; the actual method called is the remove method in java.util.RegularEnumSet:

image.png

clear() button

Using 五行 as an example, click the clear() button to empty the EnumSet.

image.png

The change in the elements field can be seen at this point.

The clear() method is defined in the java.util.Collection interface; the actual method called is the clear() method in java.util.RegularEnumSet:

image.png

Operation log panel

The Operation log panel at the bottom records each operation. The log at startup is as follows:

image.png

The first line states the currently selected enum type. The second line reminds you that before calling a factory method, add(...), remove(...) and clear() are disabled (the buttons are gray). From the third line, it lists several rules that this tool can help you observe:

After that, each operation appends a line to the log and auto-scrolls to the latest line. Each line has three columns:

  1. The expression for that call, e.g. add(五行.木).
  2. The change in elements, written as before (decimal, hexadecimal in parentheses) -> after. If the operation did not change any bit, only one value is shown and no -> appears.
  3. A note in square brackets explaining what the operation did to the bits, or stating that it was a no-op.

Using the screenshot in the Adding elements section as a reference, after executing EnumSet.noneOf(五行.class) and then add(五行.木), the new log line looks like this:

add(五行.木)                        elements 0 (0x0) -> 2 (0x2)          [elements |= 1L << 1  (bit 1 set by OR)]

Reset button

This button restarts the demo.

image.png

Complete code

The complete demo is a single Swing class named RegularEnumSetBehaviorDemo. It implements the enum type selector, factory method buttons, add/remove/clear operations, the state panel, the bit grid, and the operation log described above. The full source appears in the original article.

Brief class diagram of RegularEnumSet

If generics are ignored, a brief class diagram for java.util.RegularEnumSet can be drawn:

image.png

How to draw a class diagram for java.util.RegularEnumSet?

I used the code provided in the article [Java] How to automatically generate simple PlantUML class diagrams. The key commands are:

javac ClassDiagramGenerator.java
java ClassDiagramGenerator 'java.util.RegularEnumSet'

The output of the second command is:

@startuml

interface java.lang.Cloneable
interface java.lang.Iterable
interface java.io.Serializable
interface java.util.Collection
interface java.util.Set
abstract java.util.AbstractCollection
abstract java.util.EnumSet
abstract java.util.AbstractSet
class java.util.RegularEnumSet
java.lang.Iterable <|-- java.util.Collection
java.util.Collection <|.. java.util.AbstractCollection
java.util.Collection <|-- java.util.Set
java.util.AbstractCollection <|-- java.util.AbstractSet
java.util.Set <|.. java.util.AbstractSet
java.util.AbstractSet <|-- java.util.EnumSet
java.lang.Cloneable <|.. java.util.EnumSet
java.io.Serializable <|.. java.util.EnumSet
java.util.EnumSet <|-- java.util.RegularEnumSet

@enduml

With the PlantUML plugin in IntelliJ IDEA (Community Edition), the above puml text can be converted into a class diagram.

References