Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create an instance of enum using reflection in Java?

When I'm reading Effective Java, the author told me that a single-element enum type is the best way to implement a singleton, because we don't have to consider sophisticated serialization or reflection attacks. This means we cannot create an instance of enum using reflection, right?

I have done some tests, with an enum class here:

public enum Weekday {}

Then I tried to create an instance of Weekday:

Class<Weekday> weekdayClass = Weekday.class;
Constructor<Weekday> cw = weekdayClass.getConstructor(null);
cw.setAccessible(true);
cw.newInstance(null);

As you know, it doesn't work. When I change the key word enum to class, it works. I want to know why. Thank you.

like image 859
xing.zhang Avatar asked Mar 08 '12 07:03

xing.zhang


People also ask

How do you create an instance of an enum in Java?

10) You can not create an instance of enums by using a new operator in Java because the constructor of Enum in Java can only be private and Enums constants can only be created inside Enums itself. 11) An instance of Enum in Java is created when any Enum constants are first called or referenced in code.

How do you create an instance of a reflection in Java?

We can use newInstance() method on the constructor object to instantiate a new instance of the class. Since we use reflection when we don't have the classes information at compile time, we can assign it to Object and then further use reflection to access it's fields and invoke it's methods.

What code can you write to get an instance of this enum?

Enum valueOf() An enum class automatically gets a static valueOf() method in the class when compiled. The valueOf() method can be used to obtain an instance of the enum class for a given String value. Here is an example: Level level = Level.

Can enums have instance methods?

Enum classes can also implement multiple interfaces like normal classes. Enum classes can also have constructors, instances variables and methods like normal Java classes.


2 Answers

This is built into the language. From the Java Language Specification (§8.9):

It is a compile-time error to attempt to explicitly instantiate an enum type (§15.9.1). The final clone method in Enum ensures that enum constants can never be cloned, and the special treatment by the serialization mechanism ensures that duplicate instances are never created as a result of deserialization. Reflective instantiation of enum types is prohibited. Together, these four things ensure that no instances of an enum type exist beyond those defined by the enum constants.

The whole purpose of this is to allow the safe use of == to compare Enum instances.

EDIT: See the answer by @GotoFinal for how to break this "guarantee" using reflection.

like image 154
Ted Hopp Avatar answered Oct 21 '22 20:10

Ted Hopp


It is possible to create new enum instance in runtime - but it is very bad idea and might break in any update. You can use unsafe or reflections for this.

Like at this example enum:

public enum Monster {
    ZOMBIE(Zombie.class, "zombie"),
    ORK(Ork.class, "ork"),
    WOLF(Wolf.class, "wolf");
    private final Class<? extends Entity> entityClass;
    private final String                  entityId;
    Monster(Class<? extends Entity> entityClass, String entityId) {
        this.entityClass = entityClass;
        this.entityId = "monster:" + entityId;
    }
    public Class<? extends Entity> getEntityClass() { return this.entityClass; }
    public String getEntityId() { return this.entityId; }
    public Entity create() {
        try { return entityClass.newInstance(); }
        catch (InstantiationException | IllegalAccessException e) { throw new InternalError(e); }
    }
}

We can use

Class<Monster> monsterClass = Monster.class;
// first we need to find our constructor, and make it accessible
Constructor<?> constructor = monsterClass.getDeclaredConstructors()[0];
constructor.setAccessible(true);

// this is this same code as in constructor.newInstance, but we just skipped all that useless enum checks ;)
Field constructorAccessorField = Constructor.class.getDeclaredField("constructorAccessor");
constructorAccessorField.setAccessible(true);
// sun.reflect.ConstructorAccessor -> internal class, we should not use it, if you need use it, it would be better to actually not import it, but use it only via reflections. (as package may change, and will in java 9+)
ConstructorAccessor ca = (ConstructorAccessor) constructorAccessorField.get(constructor);
if (ca == null) {
    Method acquireConstructorAccessorMethod = Constructor.class.getDeclaredMethod("acquireConstructorAccessor");
    acquireConstructorAccessorMethod.setAccessible(true);
    ca = (ConstructorAccessor) acquireConstructorAccessorMethod.invoke(constructor);
}
// note that real constructor contains 2 additional parameters, name and ordinal
Monster enumValue = (Monster) ca.newInstance(new Object[]{"CAERBANNOG_RABBIT", 4, CaerbannogRabbit.class, "caerbannograbbit"});// you can call that using reflections too, reflecting reflections are best part of java ;)

On java 9 this might not compile due to usage of internal class as I described that in comment - you can skip that using unsafe or even more reflections.

But then we also need to add that constant to enum itself, so Enum.values() will return valid list, we can do this by changing value of final field using good old trick to make final field non-final again:

static void makeAccessible(Field field) throws Exception {
    field.setAccessible(true);
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~ Modifier.FINAL);
}

And then just change that field to new value that include our new field:

Field valuesField = Monster.class.getDeclaredField("$VALUES");
makeAccessible(valuesField);
// just copy old values to new array and add our new field.
Monster[] oldValues = (Monster[]) valuesField.get(null);
Monster[] newValues = new Monster[oldValues.length + 1];
System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
newValues[oldValues.length] = enumValue;
valuesField.set(null, newValues);

There is also another field that store enum constant, so it is important to do similar trick to it too: private volatile transient T[] enumConstants = null; - in Class.class, note that it can be null - java will regenerate them on next usage.
private volatile transient Map<String, T> enumConstantDirectory = null; - in Class.class, note that it can be null too, same as field above.

So just set them to null using reflections and your new value is ready to use.
The only impossible thing without editing class using instrumentation or other tricks is to add real field to that enum for our new value.

Also it is possible to create new enum instance using Unsafe class:

public static void unsafeWay() throws Throwable {
    Constructor<?> constructor = Unsafe.class.getDeclaredConstructors()[0];
    constructor.setAccessible(true);
    Unsafe unsafe = (Unsafe) constructor.newInstance();
    Monster enumValue = (Monster) unsafe.allocateInstance(Monster.class);
}

But unsafe class does not call the constructor, so you need to init all fields manually...

Field ordinalField = Enum.class.getDeclaredField("ordinal");
makeAccessible(ordinalField);
ordinalField.setInt(enumValue, 5);

Field nameField = Enum.class.getDeclaredField("name");
makeAccessible(nameField);
nameField.set(enumValue, "LION");

Field entityClassField = Monster.class.getDeclaredField("entityClass");
makeAccessible(entityClassField);
entityClassField.set(enumValue, Lion.class);

Field entityIdField = Monster.class.getDeclaredField("entityId");
makeAccessible(entityIdField);
entityIdField.set(enumValue, "Lion");

Note that you also need to initialize internal enum fields.
Also using unsafe it should be possible to declare new class to create new instance of abstract enum classes. I used javassist library to reduce code needed to generate new class:

public class Test {
    public static void main(String[] args) throws Exception {
        System.out.println(MyEnum.VALUE.getSomething());

        ClassPool classPool = ClassPool.getDefault();
        CtClass enumCtClass = classPool.getCtClass(MyEnum.class.getName());
        CtClass ctClass = classPool.makeClass("com.example.demo.MyEnum$2", enumCtClass);

        CtMethod getSomethingCtMethod = new CtMethod(CtClass.intType, "getSomething", new CtClass[0], ctClass);
        getSomethingCtMethod.setBody("{return 3;}");
        ctClass.addMethod(getSomethingCtMethod);

        Constructor<?> unsafeConstructor = Unsafe.class.getDeclaredConstructors()[0];
        unsafeConstructor.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeConstructor.newInstance();

        MyEnum newInstance = (MyEnum) unsafe.allocateInstance(ctClass.toClass());
        Field singletonInstance = MyEnum.class.getDeclaredField("VALUE");
        makeAccessible(singletonInstance);
        singletonInstance.set(null, newInstance);

        System.out.println(MyEnum.VALUE.getSomething());
    }

    static void makeAccessible(Field field) throws Exception {
        field.setAccessible(true);
        Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(field, field.getModifiers() & ~ Modifier.FINAL);
    }
}

enum MyEnum {
    VALUE {
        @Override
        public int getSomething() {
            return 5;
        }
    };

    public abstract int getSomething();
}

This will print 5 and then 3. Note that this is impossible to enum classes that does not contain subclasses - so without any overriden methods, as then enum is declared as final class.

Source: https://blog.gotofinal.com/java/diorite/breakingjava/2017/06/24/dynamic-enum.html

like image 26
GotoFinal Avatar answered Oct 21 '22 19:10

GotoFinal