Search code examples
javareflectionenumsjvm-bytecode

At the bytecode level, how does Java's Class.getEnumConstants() know which classes are enum classes?


The Java reflection API contains a method Class.getEnumConstants() that makes it possible to determine whether or not a class is an enum class (it returns null if it doesn't think the class is an enum), and what its constants are.

I'm working on a program that generates JVM bytecode directly, and am trying to generate an enum class. As such, I need to know how Java recognises enum classes from their bytecode, so that getEnumConstants will work correctly. Obviously, the class needs to extend Enum, but that clearly isn't enough on its own (e.g. the bytecode corresponding to public class Example extends Enum<Example> {} will not be recognised as an enum); what other features does the JVM bytecode for a class need to have so that Java's reflection API will recognise it as a Java enum, and be able to determine its enum constants?


Solution

  • In order to compile an enum type, you have to mark the class with the ACC_ENUM flag in the class’ access flags.

    Further, for each constant, you have to create a corresponding public static final field which is also marked with ACC_ENUM in the field’s access flags.

    Then, you need a class initializer (a no-arg void method named <clinit>) which creates instances and assigns them to the fields.

    But that’s not enough. Mind the language specification, which specifies the existence of the two implicitly declared methods

      /**
      * Returns an array containing the constants of this enum 
      * type, in the order they're declared.  This method may be
      * used to iterate over the constants as follows:
      *
      *    for(E c : E.values())
      *        System.out.println(c);
      *
      * @return an array containing the constants of this enum 
      * type, in the order they're declared
      */
      public static E[] values();
    
      /**
      * Returns the enum constant of this type with the specified
      * name.
      * The string must match exactly an identifier used to declare
      * an enum constant in this type.  (Extraneous whitespace 
      * characters are not permitted.)
      * 
      * @return the enum constant with the specified name
      * @throws IllegalArgumentException if this enum type has no
      * constant with the specified name
      */
      public static E valueOf(String name);
    

    It’s the duty of the compiler resp. bytecode generating tool to insert their implementation to the specific enum type. Note that despite being compiler generated, these two method should not get marked as synthetic.


    The specification does not address, how Reflection will gather its information. It could iterate over the marked fields and read them, to assemble an array or it could just invoke the values() method of the specific type. So you can not omit any of these artifacts nor can you implement the values() method by just delegate to Class.getEnumConstants().