使用反射的 OpenJDK 11 动态枚举

Tho*_*mas 1 java reflection enums openjdk-11

我正在开发一个使用 JDK8 运行的项目,我们希望将其迁移到 OpenJDK11。

但是,有一些遗留代码可以在运行时动态创建枚举(使用反射和sun.reflect.*包):

public class EnumUtil {
    static Object makeEnum(...) {
        ...
        enumClass.cast(sun.reflect.ReflectionFactory.getReflectionFactory() .newConstructorAccessor(constructor).newInstance(params));
    }
}
Run Code Online (Sandbox Code Playgroud)

或者

    // before, field is made accessible, the modifier too
    sun.reflect.FieldAccessor fieldAccessor = sun.reflect.ReflectionFactory.getReflectionFactory().newFieldAccessor(field, false);
    field.set(target, value);
Run Code Online (Sandbox Code Playgroud)

例如,假设我们有枚举AEnum

public enum AEnum {
    ; // no values at compile time

    private String label;

    private AEnum (String label) {
        this.label = label;
    }
Run Code Online (Sandbox Code Playgroud)

然后,我们添加这样的枚举值:

EnumUtil.addEnum(MyEnum.class, "TEST", "labelTest");
Run Code Online (Sandbox Code Playgroud)

最后,在运行时,我们有一个带有 label = labelTest 的值AEnum.TEST(不是通过直接调用,而是通过)。Enum.valueOf

不幸的是,sun.reflect.*OpenJDK11 中不再提供类。

我尝试过使用jdk.internal.reflect.ConstructorAccessor,但出现错误java: package jdk.internal.reflect does not exist。而且我认为依赖课程并不是一个好主意jdk.internal.*

是否有任何 OpenJDK11 替代方案可以在运行时创建枚举?

Hol*_*ger 5

这个答案包含一种仅使用标准 API 的工作方法,即使使用 JDK\xc2\xa017,仍然可以工作。

\n

由于它使用 JDK 类型作为示例,--add-opens java.base/java.lang=\xe2\x80\xa6在启动时需要一个参数,这里\xe2\x80\x99s 是一个使用它自己的示例enum,不需要对环境进行任何修改。

\n
import java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.reflect.Constructor;\nimport java.util.EnumSet;\n\nclass EnumHack {\n    public static void main(String[] args) throws Throwable {\n        System.out.println(Runtime.version());\n        Constructor<Example> c\n            = Example.class.getDeclaredConstructor(String.class, int.class);\n        c.setAccessible(true);\n        MethodHandle h = MethodHandles.lookup().unreflectConstructor(c);\n        Example baz = (Example) h.invokeExact("BAZ", 42);\n        System.out.println("created Example " + baz + "(" + baz.ordinal() + \')\');\n        EnumSet<Example> set = EnumSet.allOf(Example.class);\n        System.out.println(set.contains(baz));\n        set.add(baz);\n        System.out.println(set);\n    }\n\n    enum Example {\n        FOO, BAR\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

由于它不需要特殊设置,因此可以在 Ideone 上进行演示

\n
12.0.1+12\ncreated Example BAZ(42)\nfalse\n\nException in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 42 out of bounds for length 2\n    at java.base/java.util.RegularEnumSet$EnumSetIterator.next(RegularEnumSet.java:105)\n    at java.base/java.util.RegularEnumSet$EnumSetIterator.next(RegularEnumSet.java:78)\n    at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:472)\n    at java.base/java.lang.String.valueOf(String.java:3042)\n    at java.base/java.io.PrintStream.println(PrintStream.java:897)\n    at EnumHack.main(Main.java:18)\n
Run Code Online (Sandbox Code Playgroud)\n

这不仅表明黑客完成了它的工作,而且还表明了由此引起的一些问题。应该包含所有元素的集合并不包含新常量,并且在手动添加后,所产生的不一致状态会在后续操作中产生异常。

\n

因此,这样的枚举常量不能与枚举类型的标准工具一起使用,这与问题\xe2\x80\x99s评论中所说的相匹配,它失去了作为枚举类型的优势。事实上,它\xe2\x80\x99s比这更糟糕。

\n

因此,上面显示的方法只能用作临时解决方法,或者根本不使用。

\n

  • @JohannesKuhn 该示例使用 `setAccessible(true)` ,它与 `privateLookupIn` 具有类似的效果 (2认同)