xin*_*ang 13 java reflection enums
当我阅读Effective Java时,作者告诉我单元素enum类型是实现单例的最佳方式,因为我们不必考虑复杂的序列化或反射攻击.这意味着我们无法创建enum使用反射的实例,对吧?
我做了一些测试,enum这里有一个类:
public enum Weekday {}
Run Code Online (Sandbox Code Playgroud)
然后我尝试创建一个实例Weekday:
Class<Weekday> weekdayClass = Weekday.class;
Constructor<Weekday> cw = weekdayClass.getConstructor(null);
cw.setAccessible(true);
cw.newInstance(null);
Run Code Online (Sandbox Code Playgroud)
如你所知,它不起作用.当我改变关键词enum时class,它有效.我想知道为什么.谢谢.
Ted*_*opp 18
这是内置于语言中的.从Java语言规范(第8.9节):
尝试显式实例化枚举类型(第15.9.1节)是编译时错误.Enum中的最终克隆方法确保永远不会克隆枚举常量,并且序列化机制的特殊处理可确保不会因反序列化而创建重复实例.禁止对枚举类型进行反射实例化.总之,这四件事确保除了枚举常量定义的实例之外不存在枚举类型的实例.
这样做的全部目的是允许安全地使用==比较Enum实例.
编辑:请参阅@GotoFinal的答案,了解如何使用反射打破这种"保证".
Got*_*nal 12
可以在运行时创建新的枚举实例 - 但这是一个非常糟糕的主意,可能会在任何更新中中断.您可以使用不安全或反射.
就像在这个例子枚举:
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); }
}
}
Run Code Online (Sandbox Code Playgroud)
我们可以用
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 ;)
Run Code Online (Sandbox Code Playgroud)
在java 9上,由于我在评论中描述的内部类的使用,这可能无法编译 - 你可以使用不安全甚至更多的反射来跳过它.
但是我们还需要添加该常量以枚举自身,因此Enum.values()将返回有效列表,我们可以通过使用旧的技巧更改最终字段的值来再次使最终字段非最终:
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);
}
Run Code Online (Sandbox Code Playgroud)
然后只需将该字段更改为包含新字段的新值:
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);
Run Code Online (Sandbox Code Playgroud)
还有另一个存储枚举常量的字段,因此对它执行类似的技巧也很重要:
private volatile transient T[] enumConstants = null;- Class.class请注意它可以为null - java将在下次使用时重新生成它们.
private volatile transient Map<String, T> enumConstantDirectory = null;- Class.class请注意,它也可以为null,与上面的字段相同.
因此,只需使用反射将它们设置为null,即可使用新值.
没有使用仪器或其他技巧编辑类的唯一不可能的事情是为我们的新值添加真实字段到该枚举.
也可以使用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);
}
Run Code Online (Sandbox Code Playgroud)
但是不安全的类不会调用构造函数,因此您需要手动初始化所有字段...
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");
Run Code Online (Sandbox Code Playgroud)
请注意,您还需要初始化内部枚举字段.
同样使用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();
}
Run Code Online (Sandbox Code Playgroud)
这将打印5然后打印3.注意,这不可能枚举不包含子类的类 - 因此没有任何重写方法,因为枚举被声明为最终类.
来源:https://blog.gotofinal.com/java/diorite/breakingjava/2017/06/24/dynamic-enum.html
小智 8
这可能是一个死的帖子,但你可以得到一个声明使用的每个常量的实例Weekday.class.getEnumConstants().这将返回所有常量的数组,其中获取单个实例是微不足道的getEnumConstants()[0].
小智 6
因此,如果您的目标是持久化然后重建枚举信息。您将需要保留 enumClassName 及其值。
public enum DaysOfWeek{ Mon, Tue, Wed, Thu, Fri, Sat, Sun }
DaysOfWeek dow = DaysOfWeek.Tue;
String value = dow.toString();
String enumClassName = dow.getClass().getName();
// Persist value and enumClassName
// ...
// Reconstitute the data
Class clz = Class.forName(enumClassName);
Object o = Enum.valueOf(clz, value);
DaysOfWeek dow2 = (DaysOfWeek)o;
System.out.println(dow2);
Run Code Online (Sandbox Code Playgroud)