跪拜 Guibai
← All articles
Backend · AI Programming · Java

Java Enums: Why Abstract Methods Work, and the Anonymous Subclass Behind Every Constant

By 金銀銅鐵 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Code that compares enum values by `getClass()` will break for constants that define class bodies, since those check the anonymous subclass rather than the enum type. The difference also affects serialization, logging, and any reflection that relies on `isEnum()` or `getEnumConstants()`, which return false and null respectively for those anonymous classes.

Summary

The Java Language Specification allows an abstract method inside an enum, and every constant with a class body becomes an anonymous final subclass of the enum type. The abstract method's implementation lives in those constant bodies, so calling `PLUS.eval(1, 2)` actually invokes a method on `Operation$1`, not on `Operation` itself.

The behavior shows up in the reflection API: for constants with class bodies, `getClass()` returns a compiler-generated name like `Operation$3`, while `getDeclaringClass()` always returns the enum type. JDK's own `Enum.compareTo` relies on this distinction. Empty class bodies still produce subclasses, and any enum with at least one such constant is implicitly sealed, with the permitted subclasses being exactly those anonymous classes.

A Swing-based GUI explorer built for this post walks through three sample enums: one with no class bodies, one where every constant has a body, and one with a mix. The tool displays the hierarchy, the source with the selected constant highlighted, and a nine-row fact table comparing `getClass()` and `getDeclaringClass()` for each constant.

Takeaways
— JLS §8.9.1: a constant's class body implicitly declares an anonymous final class that extends the enclosing enum.
— Abstract methods in an enum are legal because every constant with a class body provides a concrete implementation; missing one causes a compile error.
— For constants with class bodies, getClass() returns a runtime class like Operation$3, while getDeclaringClass() always returns the enum type.
— An empty class body like ON_HOLD { } still generates a separate subclass; even without any methods, getClass() differs from the enum class.
— If at least one constant has a class body, the enum class becomes implicitly sealed, and getPermittedSubclasses() lists exactly the anonymous subclasses.
— The class file names like Operation$1 are compiler-specific; JLS 13.1 leaves them implementation-determined, so javac's numbering may differ across compilers.
— Enum.compareTo() in the JDK uses getClass() first, then falls back to getDeclaringClass(), because most enums have no class bodies and are equal at the getClass() level.
Conclusions

The subtlety that an empty class body still produces a distinct subclass is easy to miss; it means any constant that even marks a body changes the runtime type, which can surprise code doing strict class checks.

The compiler marks the enum class abstract when an abstract method is present, but the JLS grants an exception to the normal rule that abstract methods require an abstract class, so the enum itself stays instantiable through constants.

The GUI tool's emphasis on comparing getClass() with getDeclaringClass() is a practical debugging aid for anyone who has seen unexpected Operation$1 names in logs or serialized data.

Concepts & terms
Constant-specific class body
An optional body attached to an enum constant, written as { ... }, which implicitly declares an anonymous subclass of the enum type. It can override methods or supply implementations for abstract methods declared in the enum.
getDeclaringClass() vs getClass()
getClass() returns the runtime class of an object, which for an enum constant with a class body is the anonymous subclass (e.g., Operation$1). getDeclaringClass() returns the enum type that declares the constant (e.g., Operation), even when the runtime class is a subclass.
Implicitly sealed enum
An enum class becomes sealed if at least one of its constants has a class body. The permitted direct subclasses are exactly the anonymous classes generated from those constant bodies, and the compiler prevents any other subclassing.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗