Enums — Professional¶
What? Bytecode of
enum:ACC_ENUMflag,Enum<E>superclass, synthetic methods (values,valueOf), per-constant anonymous subclasses,<clinit>order, and how the verifier prevents reflective enum instantiation. How? Withjavap -v, study the JDK'sEnum.javaandEnumSet.javasource, observe class loading with-Xlog:class+init.
1. Class file flags¶
An enum compiles with: - ACC_ENUM (0x4000) on the class - ACC_FINAL (0x0010) — enums can't be extended (except via per-constant anonymous subclasses) - Each constant field has ACC_ENUM | ACC_PUBLIC | ACC_STATIC | ACC_FINAL
$ javap -v Direction.class | head
public final class Direction extends java.lang.Enum<Direction>
flags: (0x4030) ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
Wait — ACC_FINAL and the ability to override via anonymous subclasses? The JVM allows this because the per-constant subclasses are auto-generated. User code can't extend the enum.
2. Synthetic methods¶
The compiler generates:
values() returns a clone of the internal Direction[] $VALUES. The clone prevents callers from mutating the global array.
valueOf(String) calls Enum.valueOf(Class, String) which uses an internal name → constant map.
3. The Enum<E> superclass¶
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable {
private final String name;
private final int ordinal;
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
public final String name() { return name; }
public final int ordinal() { return ordinal; }
public final int compareTo(E o) { return ordinal - o.ordinal; }
public final boolean equals(Object o) { return this == o; }
public final int hashCode() { return super.hashCode(); }
public final Class<E> getDeclaringClass() { ... }
// ...
}
equals is == — exactly what you'd want for singletons. hashCode uses identity. compareTo is by ordinal. These are all final — you can't override.
4. Per-constant anonymous subclasses¶
public enum Op {
PLUS { public int apply(int a, int b) { return a + b; } };
public abstract int apply(int a, int b);
}
Compiles to: - Op — abstract class with abstract apply - Op$1 — anonymous subclass implementing apply for PLUS
$ javap -v Op.class | grep extends
public abstract class Op extends java.lang.Enum<Op>
$ javap -v 'Op$1.class'
final class Op$1 extends Op
flags: ACC_FINAL, ACC_SUPER, ACC_ENUM
Each constant is one anonymous final subclass.
5. <clinit> for enums¶
public static {};
Code:
0: new #2 // class Op$1
3: dup
4: ldc #4 // String PLUS
6: iconst_0
7: invokespecial #5 // Method Op$1."<init>":(Ljava/lang/String;I)V
10: putstatic #6 // Field PLUS:LOp;
...
<similar block for each constant>
last: putstatic $VALUES
last+1: return
Constants are constructed in declaration order, then stored in the $VALUES array. After <clinit>, all constants exist.
6. Class.isEnum() and reflection¶
Class.isEnum() returns true if ACC_ENUM is set on the class. Class.getEnumConstants() returns the values.
Constructor.newInstance on an enum throws:
This is enforced by the JVM, not just javac. Protects singleton invariants.
7. EnumSet implementations¶
Two concrete classes:
RegularEnumSet<E>— uses a singlelongbitset, for ≤64 constantsJumboEnumSet<E>— useslong[], for >64 constants
Created via EnumSet.of(...), EnumSet.allOf(...), etc. — factory methods choose the right impl based on constant count.
8. EnumMap internals¶
EnumMap<K, V> stores values in Object[] vals indexed by ordinal. get(key) is vals[key.ordinal()]. put updates the same slot.
Iteration order is declaration order (ordinal 0 to N).
Internally maintains a Class<K> keyType for runtime checks (used in containsKey etc.).
9. Switch on enum bytecode¶
Compiles to a tableswitch via an indirection. The compiler doesn't directly use ordinal() in the bytecode (because that would break if the enum is recompiled with reordered constants). Instead:
- A synthetic class
EnumDesugaris generated holding anint[]mapping ordinals to switch indices. - The switch reads
EnumDesugar.$SwitchMap$Op[op.ordinal()]to get the index. - Then
tableswitchjumps based on that index.
This indirection means recompiling the enum and not the switch is safe (the synthetic map reflects the new enum's ordering).
10. Pattern matching switch on enum (Java 21+)¶
In Java 21+, this can compile to an invokedynamic to SwitchBootstraps.enumSwitch, which is even cleaner.
11. Records as enum-like¶
vs
public sealed interface HttpMethod permits Get, Post, Put { }
public record Get() implements HttpMethod { }
public record Post() implements HttpMethod { }
public record Put() implements HttpMethod { }
Both express closed variants. Enum is more compact for label-only sets. Sealed + records is necessary when variants carry data.
12. Enum in JPMS¶
Enums in modules behave like normal classes. Access modifiers apply. EnumSet/EnumMap work across modules normally.
Class.forName for enums respects module boundaries. Loading an enum from a different module requires the package to be exported.
13. Verifier rules for enums¶
JVMS §4.10 includes special rules: - Enum classes must extend Enum<E> directly. - Cannot have public constructors (only private synthesized one). - Cannot be instantiated via new from outside the class.
These prevent the singleton pattern from being violated.
14. Where the spec says it¶
| Topic | Source |
|---|---|
| Enum types | JLS §8.9 |
| Enum constants | JLS §8.9.1 |
| Enum body | JLS §8.9.2 |
| Enum methods (synthesized) | JLS §8.9.3 |
| Enum constructors | JLS §8.9.2 |
Class.isEnum() | java.lang.Class Javadoc |
Enum<E> API | java.lang.Enum Javadoc |
ACC_ENUM flag | JVMS §4.1, §4.5 |
| EnumSet | java.util.EnumSet Javadoc |
| EnumMap | java.util.EnumMap Javadoc |
Memorize this: enum compiles to a final class extends Enum<E> with ACC_ENUM flag. Per-constant overrides become anonymous subclasses. <clinit> constructs all constants. EnumSet/EnumMap use ordinals for O(1) array indexing. Reflection cannot create new enum instances. The JIT often devirtualizes enum dispatch.