Можно создать новый экземпляр enum во время выполнения, но это очень плохая идея, которая может сломаться при любом обновлении. Вы можете использовать небезопасные или отражения для этого.
Как в этом примере 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); }
}
}
Мы можем использовать
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 ;)
В java 9 это может не скомпилироваться из-за использования внутреннего класса, как я описал в комментарии - вы можете пропустить это, используя небезопасные или даже больше отражений.
Но затем нам также нужно добавить эту константу к самому enum, так что Enum.values () вернет действительный список, мы можем сделать это, изменив значение поля final, используя старый добрый трюк, чтобы снова сделать поле final не финальным:
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);
}
А затем просто измените это поле на новое значение, которое включает наше новое поле:
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);
Существует также другое поле, в котором хранится константа перечисления, поэтому важно сделать с ней аналогичный трюк:
private volatile transient T[] enumConstants = null;
- в Class.class
обратите внимание, что оно может быть нулевым - java восстановит их при следующем использовании.
private volatile transient Map<String, T> enumConstantDirectory = null;
- в Class.class
обратите внимание, что он может быть также нулевым, как и поле выше.
Так что просто установите их в null, используя отражения, и ваше новое значение готово к использованию.
Единственная невозможная вещь без редактирования класса с использованием инструментария или других хитростей - это добавить к этому перечислению реальное поле для нашего нового значения.
Также возможно создание нового экземпляра enum с использованием класса Unsafe:
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);
}
Но небезопасный класс не вызывает конструктор, поэтому вам нужно инициализировать все поля вручную ...
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");
Обратите внимание, что вам также нужно инициализировать внутренние поля перечисления.
Также с использованием unsafe должна быть возможность объявить новый класс для создания нового экземпляра абстрактных перечислимых классов. Я использовал библиотеку javassist для уменьшения кода, необходимого для создания нового класса:
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();
}
Это выведет 5, а затем 3. Обратите внимание, что невозможно перечислить классы, которые не содержат подклассов - поэтому без каких-либо переопределенных методов, как тогда enum объявляется как конечный класс.
Источник: https://blog.gotofinal.com/java/diorite/breakingjava/2017/06/24/dynamic-enum.html