此方法是在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源码后发现

image

原来枚举类的默认构造器是会隐形的加入一个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)

你会发现原来代码底层直接把枚举类锁死了不让我们创建, 定位到报错的关键位置

image-1696768530259

你会发现这个会进行一次校验,如果是ENUM就会出现这个错误,但是可以看见代码494行向下的才是真正的逻辑,我们可以用反射去反射这个Constructor里面的方法,从而绕过限制,但是又会发现一个特别坑的地方

image-1696768793432

当你尝试反射Constructor#reflectionFactory这个变量的时候,你会发现这TMD哪冒出来的,最后得出结论是,源码跟实际运行的代码是不一致的

image-1696768915440

最后找到了原来藏在了这里,最后代码小改动亿下

@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

image-1696769072630

运行结果

image-1696769138262