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:
The two classes below are both subclasses of EnumSet:
java.util.RegularEnumSetjava.util.JumboEnumSet
The exact type returned by java.util.EnumSet#noneOf falls into two cases:
- When the number of elements n in the enum type satisfies n <= 64,
java.util.EnumSet#noneOfreturns an instance ofjava.util.RegularEnumSet. - When n > 64, it returns an instance of
java.util.JumboEnumSet.
In java.util.RegularEnumSet, a field named elements can be found:
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).
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:
RegularEnumSetBehaviorDemo.classRegularEnumSetBehaviorDemo$BitGrid.classRegularEnumSetBehaviorDemo$DemoEnumType.classRegularEnumSetBehaviorDemo$Planet.classRegularEnumSetBehaviorDemo$地支.classRegularEnumSetBehaviorDemo$方位.classRegularEnumSetBehaviorDemo$天干.classRegularEnumSetBehaviorDemo$五行.class
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
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:
runtime class: the runtime type ofset, namelyjava.util.RegularEnumSet. This line directly confirms the conclusion from the background section:noneOfreally returns an instance ofRegularEnumSet.elements(decimal): the decimal value ofelements, with its hexadecimal form in parentheses.elements(binary): the binary form ofelements, displayed in bit groups.size: the size ofset, i.e.Long.bitCount(elements).RegularEnumSetdoes not store the element count separately;size()directly counts the1bits inelements.set: the enum constants currently contained in theEnumSet.
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:
Planet- 五行
- 天干
- 地支
- 方位
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:
EnumSet.noneOf(五行.class)EnumSet.allOf(五行.class)EnumSet.range(五行.金, 五行.水)(you can choose different start and end elements)
The buttons for the three static factory methods are marked with red frames in the image below:
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:
- The Operation log appends a line recording the thrown exception.
- A red banner appears at the top of the interface, showing the ordinals of
fromandto. - The control group containing
from/tois outlined in red.
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:
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:
Removing elements
Using 五行 as an example, select one element in 五行 and execute the corresponding remove method:
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:
clear() button
Using 五行 as an example, click the clear() button to empty the EnumSet.
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:
Operation log panel
The Operation log panel at the bottom records each operation. The log at startup is as follows:
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:
noneOfmakeselementsequal0.allOfmakeselementsequal(1L << n) - 1.addsets the target bit to1, i.e.elements |= (1L << ordinal).removeclears the target bit and leaves the other bits unchanged, i.e.elements &= ~(1L << ordinal). Here~operates on the mask1L << ordinal(turning only the target bit set into only the target bit clear), then ANDs with the original value.clear()makeselementsbecome0.- Bit i is
1if and only if the constant with ordinal i is a member.
After that, each operation appends a line to the log and auto-scrolls to the latest line. Each line has three columns:
- The expression for that call, e.g.
add(五行.木). - 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. - 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)]
- Column 1
add(五行.木): call expression. - Column 2
elements 0 (0x0) -> 2 (0x2): changed from 0 to 2 (binary0b10), meaningbit 1is set. - Column 3
[elements |= 1L << 1 (bit 1 set by OR)]: the bit operation corresponding to this call.
Reset button
This button restarts the demo.
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:
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
- [Java] Showing the internal changes of ArrayList when adding elements (I asked Trae to refer to this article)
- [Java] How to automatically generate simple PlantUML class diagrams (I referred to this article when drawing the class diagram for java.util.RegularEnumSet)
- [Java] Using CLI to show the internal changes when adding/removing elements in EnumSet (Part 1)
- [Java] Using CLI to show the internal changes when adding/removing elements in EnumSet (Part 2)