Previous in the Series
Current Tutorial
Working with Enumerations
Next in the Series

Previous in the Series: Working with Arrays

Next in the Series: Working with Records

Working with Enumerations

Since enums are classes, reflection has no need to define an explicit java.lang.reflect.Enum class, and indeed, there is none. The only Reflection APIs that are specific to enums are Class.isEnum(), Class.getEnumConstants(), and Field.isEnumConstant(). Most reflective operations involving enums are the same as any other class or member. For example, enum constants are implemented as public static final fields on the enum. The following sections show how to use Class and Field with enums.

 

Getting Access to the Class and the Constants

Reflection provides three enum-specific APIs:

Sometimes it is necessary to dynamically retrieve the list of enum constants; in non-reflective code this is accomplished by invoking the implicitly declared static method values() on the enum. If an instance of an enum type is not available the only way to get a list of the possible values is to invoke Class.getEnumConstants() since it is impossible to instantiate an enum type.

Suppose that you have the following enum.

public enum Days {
    SATURDAY, SUNDAY
}

Then you can write the following code to reflectively get access to different elements of this enum.

Class<?> c = Days.class;
System.out.println("class: " + c.getName());

System.out.println("values: " + Arrays.toString(Days.values()));
System.out.println("constants: " + Arrays.toString(c.getEnumConstants()));

Running the previous code prints the following. As you can see, the call to c.getEnumConstants() returns the same array as the one you get with Days.values().

class: org.devjava.Days
values: [SATURDAY, SUNDAY]
constants: [SATURDAY, SUNDAY]

 

Locating Enum Constants

Thanks to the Reflection API, you can locate the constructors of this Days enum.

Class<Days> c = Days.class;
Constructor<?>[] constructors = c.getDeclaredConstructors();
int length = constructors.length;
System.out.println("# constructors = " + length);
System.out.println("constructors[0]: " + constructors[0]);

Running the previous code gives you the following. As you can see, your Days enum has one constructor, which is private, and takes two parameters:

  • a String, which is the name of a constant
  • and an int, which is the index of this constant in the array of the constants this enum defines.
# constructors = 1
constructors[0]: private org.devjava.Days(java.lang.String,int)

You can then try to get a reference on this constructor, and invoke it to create another constants in the Days enum.

Class<Days> c = Days.class;
Constructor<Days> constructor = c.getDeclaredConstructor(String.class, int.class);
System.out.println("constructor = " + constructor);

constructor.setAccessible(true);
Days monday = constructor.newInstance("MONDAY", 2);

Running this code prints the following.

constructor = private org.devjava.Days(java.lang.String,int)
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    at java.base/java.lang.reflect.Constructor.acquireConstructorAccessor(Constructor.java:547)
    at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
    at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486)
    at org.devjava.Main.main(ScrapMain.java:42)

Indeed, it is not possible to add constants to an enum at runtime, even using the Reflection API.

 

Getting Access to Members

Suppose now that you have the following enum. Technically speaking we know that the Sun is not a planet, but the people who named the days of the week believed so at the time.

public enum Days {
    SATURDAY("Saturn"), SUNDAY("Sun");

    private final String planet;

    Days(String planet) {
        this.planet = planet;
    }

    public static Days of(String label) {
        return Arrays.stream(values())
                .filter(day -> day.planet.equals(label))
                .findAny().orElseThrow();
    }
    
    public String planet() {
        return this.planet;
    }
}

Note that this enum as a defined constructor, a field, and a method, which is an accessor to this field. It also has a factory method, to get one of the constant from a label.

You can discover the number of constructors this Days enum has.

Class<Days> c = Days.class;
Constructor<?>[] constructors = c.getDeclaredConstructors();
int length = constructors.length;
System.out.println("# constructors = " + length);

Running the previous code prints the following. This enum still has only one constructor: the one you defined. But as you can see, it takes more parameters than the one you defined. The compiler added the name of this constant, and its index. It is still not possible to invoke this constructor reflectively to create more constants at runtime.

# constructors = 1
constuctor[0]: private org.devjava.Days(java.lang.String,int,java.lang.String)

All the other members of your enum can be accessed reflectively, as any member of any regular class.

 

Discovering Compiler Generated Fields

The Reflection API can give you access to the internal structure of the Days class that the compiler generated for you.

Let us start with the fields. Some of them are static, so to get their value you need to call field.get(null), because a static field doesn't depend on any object. And some of them are arrays. Thus, the special processing in the following code.

Class<Days> c = Days.class;
Field[] fields = c.getDeclaredFields();

for (Field field : fields) {
    System.out.println(field);
    if (Modifier.isStatic(field.getModifiers())) {
        field.setAccessible(true);
        if (field.getType().isArray()) {
            System.out.println("  " + Arrays.toString((Object[])field.get(null)));
            System.out.println("    is enum constant? " + field.isEnumConstant());
        } else {
            System.out.println("  " + field.get(null));
            System.out.println("    is enum constant? " + field.isEnumConstant());
        }
    }
}

Running the previous code prints the following. There are several points worth mentioning.

  • The compiler creates one public static field for each constant that you define in your enum, with the name of this constant.
  • It also creates a private static array called $VALUES, with all the constants you defined in it. This array is returned by the Days.values() call.
public static final org.paumard.ScrapMain$Days org.paumard.ScrapMain$Days.SATURDAY
  SATURDAY
    is enum constant? true
public static final org.paumard.ScrapMain$Days org.paumard.ScrapMain$Days.SUNDAY
  SUNDAY
    is enum constant? true
private static final org.paumard.ScrapMain$Days[] org.paumard.ScrapMain$Days.$VALUES
  [SATURDAY, SUNDAY]
    is enum constant? false

 

Discovering Compiler Generated Methods

You can also discover the methods that the compiler created for you.

Class<Days> c = Days.class;
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
    System.out.println(method);
}

Running this code prints the following. You can see the methods created by the compiler.

  • Days.values(): this is the classical method that returns the array of the constants you defined.
  • Days.valueOf(String): this is also a classical method, the one that returns a constant from its name.
  • Days.$values(): this is a private method created by the compiler.

The Days.lambda$of$0(String, Days) method corresponds to the predicate created in the of() method.

public static org.devjava.Days[] org.devjava.Days.values()
public static org.devjava.Days org.devjava.Days.valueOf(java.lang.String)
public static org.devjava.Days org.devjava.Days.of(java.lang.String)
private static org.devjava.Days[] org.devjava.Days.$values()
private static boolean org.devjava.Days.lambda$of$0(java.lang.String,org.devjava.Days)
public java.lang.String org.devjava.Days.planet()

Last update: July 25, 2024


Previous in the Series
Current Tutorial
Working with Enumerations
Next in the Series

Previous in the Series: Working with Arrays

Next in the Series: Working with Records