此方法是在
java9+
才管用, 低版本需要自行参考其他方式
本文章以及代码诞生于我无聊时候的产物
Java用反射来实例化一个枚举(java8-)
enum AK {
AK_47, AK_74;
}
@Test
public void testCase() throws Exception {
Class<AK> akClass = AK.class;
Constructor<AK> constructor = akClass.getDeclaredConstructor(String.class, int.class);
Class<Constructor> cClass = Constructor.class;
Method acquireConstructorAccessor = cClass.getDeclaredMethod("acquireConstructorAccessor");
acquireConstructorAccessor.setAccessible(true);
ConstructorAccessor ca = (ConstructorAccessor) acquireConstructorAccessor.invoke(constructor);
Object AK_103 = ca.newInstance(new Object[]{"AK_103", 3});
System.out.println(AK_103);
}
Java用反射来实例化一个枚举(java9+)
首先我线贴出枚举代码
enum E {
A, B, C;
E() {
}
}
一个很普通的E
枚举类
@Test
public void testCaseBase() throws Exception {
Constructor<E> constructor = E.class.getDeclaredConstructor();
constructor.setAccessible(true);
E e = constructor.newInstance();
System.out.println(e);
}
按照直观感受,反射只需要拿到他的默认构造器直接附上权限就可以创建,实际结果为
java.lang.NoSuchMethodException: runstatic.leetcode.EnumReflectCreateTest$E.<init>()
然后发现???找不到默认方法,查找java.lang.Enum
源码后发现
原来枚举类的默认构造器是会隐形的加入一个name
名称还有ordinal
排名的参数,然后我继续修改我的代码
@Test
public void testCaseBase2() throws Exception {
Constructor<E> constructor = E.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
E d = constructor.newInstance("D", 4);
System.out.println(d);
}
改完了代码后发现还报错
java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:492)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
at runstatic.leetcode.EnumReflectCreateTest.testCaseBase2(EnumReflectCreateTest.java:35)
你会发现原来代码底层直接把枚举类锁死了不让我们创建, 定位到报错的关键位置
你会发现这个会进行一次校验,如果是ENUM
就会出现这个错误,但是可以看见代码494行向下的才是真正的逻辑,我们可以用反射去反射这个Constructor
里面的方法,从而绕过限制,但是又会发现一个特别坑的地方
当你尝试反射Constructor#reflectionFactory
这个变量的时候,你会发现这TMD哪冒出来的,最后得出结论是,源码跟实际运行的代码是不一致的
最后找到了原来藏在了这里,最后代码小改动亿下
@Test
public void testCase() throws Exception {
// -ea --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED
Class<?> ReflectionFactory = Class.forName("jdk.internal.reflect.ReflectionFactory");
Field field = ReflectionFactory.getDeclaredField("soleInstance");
Method setAccessible0 = AccessibleObject.class.getDeclaredMethod("setAccessible0", boolean.class);
setAccessible0.setAccessible(true);
setAccessible0.invoke(field, true);
Object reflectionFactory = field.get(null);
Method newConstructorAccessor = ReflectionFactory.getDeclaredMethod("newConstructorAccessor", Constructor.class);
Constructor<E> constructor = E.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
Class<?> constructorAccessor = Class.forName("jdk.internal.reflect.ConstructorAccessor");
Method newInstance = constructorAccessor.getDeclaredMethod("newInstance", Object[].class);
Object ca = newConstructorAccessor.invoke(reflectionFactory, constructor);
E d = (E) newInstance.invoke(ca, new Object[]{ new Object[]{ "D", 4 } });
System.out.println(d + ": " + d.ordinal());
System.out.println(Arrays.toString(E.values()));
}
记得加上VM参数,要不然出现访问权限问题 -ea --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED
运行结果