首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >使用反射的OpenJDK 11动态枚举

使用反射的OpenJDK 11动态枚举
EN

Stack Overflow用户
提问于 2022-01-17 17:46:59
回答 2查看 569关注 0票数 1

我正在处理一个使用JDK8运行的项目,我们希望将它迁移到OpenJDK11。

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

代码语言:javascript
运行
复制
public class EnumUtil {
    static Object makeEnum(...) {
        ...
        enumClass.cast(sun.reflect.ReflectionFactory.getReflectionFactory() .newConstructorAccessor(constructor).newInstance(params));
    }
}

代码语言:javascript
运行
复制
    // before, field is made accessible, the modifier too
    sun.reflect.FieldAccessor fieldAccessor = sun.reflect.ReflectionFactory.getReflectionFactory().newFieldAccessor(field, false);
    field.set(target, value);

例如,假设我们有枚举AEnum

代码语言:javascript
运行
复制
public enum AEnum {
    ; // no values at compile time

    private String label;

    private AEnum (String label) {
        this.label = label;
    }

然后,我们添加如下枚举值:

代码语言:javascript
运行
复制
EnumUtil.addEnum(MyEnum.class, "TEST", "labelTest");

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

不幸的是,sun.reflect.*类在OpenJDK11中不再可用。

我尝试过使用jdk.internal.reflect.ConstructorAccessor,但是我得到了错误java: package jdk.internal.reflect does not exist。我不认为依赖jdk.internal.*类是个好主意。

在运行时创建枚举有任何OpenJDK11替代方案吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2022-01-21 15:33:33

正如您在注释部分中提到的那样,在运行时添加enum值是一个非常糟糕的主意,它打破了枚举的契约。

因此,我已经将所有的情况修改为一个POJO对象,维护一个映射。

在简化格式中,AEnum变成:

代码语言:javascript
运行
复制
public class AEnum extends DynamicEnum { // DynamicEnum has utility methods in order to act as close as a real enum.
    @Getter
    private String label;

    private static final Map<String, AEnum> map = new LinkedHashMap<>();

    protected AEnum (String name, String label) {
        this.name = name;
        this.label= label;
    }

    public static AEnum addInMap(String name, String label) {
        AEnum value = new DossierSousType(name, label);
        map.put(name, value);
        return value;
    }
}

我们从数据库中读取动态值,因此我创建了一个实用程序类来加载所有内容。

代码语言:javascript
运行
复制
public static <T extends DynamicEnum> T addEnum(final Class<T> type, final String name, final String label) throws TechnicalException {
    try {
        Method method = type.getDeclaredMethod("addInMap", String.class, String.class);
        return (T) method.invoke(null, name, label);
    } catch (... e) {
        // ...
    }
}

然后:

代码语言:javascript
运行
复制
addEnum(AEnum.class, "TEST", "labelTest");
addEnum(AEnum.class, "TEST2", "labelTest2");
AEnum.getAll() // returns a list with the two entries

此外,如果我们在持久化的Entity中使用这个"false enum“,我们就有一个转换器来管理StringAEnum之间的转换。

代码语言:javascript
运行
复制
@Entity
@Table(name = TABLE_NAME)
...
public class MyEntity {
    @Column(name = COLUMN_TYPE)
    @Convert(converter = AEnumConverter.class)
    private AEnum type;

AEnumConverter实现了javax.persistence.AttributeConverter

代码语言:javascript
运行
复制
@Converter
public class AEnumConverter implements AttributeConverter<AEnum , String> {

    @Override
    public String convertToDatabaseColumn(AEnum type) {
        return type != null ? type.getName() : null;
    }

    @Override
    public AEnum convertToEntityAttribute(String type) {
        return AEnum .getEnum(type);
    }
}

有了这个机制,一切都可以完美地工作,我们不再需要sun.reflect.*

票数 -1
EN

Stack Overflow用户

发布于 2022-01-19 14:54:46

这个答案包含一种工作方法,它只使用标准的API,而且仍然有效,即使使用JDK 17也是如此。

由于它在示例中使用了JDK类型,它在启动时需要一个--add-opens java.base/java.lang=…参数,因此这里有一个使用自己的enum类型的示例,它不需要对环境进行任何修改。

代码语言:javascript
运行
复制
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.util.EnumSet;

class EnumHack {
    public static void main(String[] args) throws Throwable {
        System.out.println(Runtime.version());
        Constructor<Example> c
            = Example.class.getDeclaredConstructor(String.class, int.class);
        c.setAccessible(true);
        MethodHandle h = MethodHandles.lookup().unreflectConstructor(c);
        Example baz = (Example) h.invokeExact("BAZ", 42);
        System.out.println("created Example " + baz + "(" + baz.ordinal() + ')');
        EnumSet<Example> set = EnumSet.allOf(Example.class);
        System.out.println(set.contains(baz));
        set.add(baz);
        System.out.println(set);
    }

    enum Example {
        FOO, BAR
    }
}

因为它不需要特殊的设置,所以可以是在Ideone上演示

代码语言:javascript
运行
复制
12.0.1+12
created Example BAZ(42)
false

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 42 out of bounds for length 2
    at java.base/java.util.RegularEnumSet$EnumSetIterator.next(RegularEnumSet.java:105)
    at java.base/java.util.RegularEnumSet$EnumSetIterator.next(RegularEnumSet.java:78)
    at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:472)
    at java.base/java.lang.String.valueOf(String.java:3042)
    at java.base/java.io.PrintStream.println(PrintStream.java:897)
    at EnumHack.main(Main.java:18)

这不仅表明了黑客的作用,而且也表明了由此引发的一些问题。假定包含所有元素的集合不包含新的常量,在手动添加它之后,生成的不一致状态会在后续操作中产生异常。

因此,这种枚举常量不能与标准的枚举类型工具一起使用,这与问题的注释中所说的相同,它失去了作为枚举类型的优势。事实上,这比那更糟。

因此,以上所示的方法只应用作临时工作,或根本不应使用。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70745514

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档