Enum遵循静态初始化程序的65535字节限制...最好的做法是什么?

Dan*_*ner 19 java compiler-construction enums limit

我已经开始了一个相当大的所谓的Descriptors枚举,我想在我的模型中用作参考列表.但是现在我第一次遇到编译器/ VM限制因此我正在寻找最好的解决方案来处理这个问题.

这是我的错误:静态初始化程序的代码超过65535字节限制

很明显这是从哪里来的 - 我的Enum只有很多元素.但是我需要那些元素 - 没有办法减少那个元素.

最初,我计划使用单个枚举,因为我想确保Enum中的所有元素都是唯一的.它用于Hibernate持久化上下文,其中对Enum的引用在数据库中存储为String值.所以这必须是独一无二的!

我的Enum的内容可以分成几组属于一起的元素.但拆分Enum会消除我在编译期间获得的独特安全性.或者可以通过某种方式实现多个枚举?

我目前唯一的想法是定义一些名为Descriptor的接口,并编写几个实现它的Enum.这样我希望能够使用Hibernate Enum映射,就好像它是一个Enum一样.但我甚至不确定这是否有效.我放松了独特的安全.

任何想法如何处理这种情况?

Ste*_*n C 8

简单.不要enum用于此.你不能.它不会起作用.

可能是您的源代码没有明确引用许多枚举值.相反,您使用枚举作为在唯一对象实例和字符串名称之间进行映射的便捷方式.因此,只需使用显式管理映射的类型替换枚举类型,通过从文件或数据库读取来初始化它.如果你做得对,你将获得枚举的计算属性和类型安全性.你唯一丢失的是语法糖......和静电.

这种方法的另一个优点是,您可以修改"描述符"映射,而无需修改程序的源代码.


顺便说一句,您遇到的限制是由JVM类文件格式强加的.方法或构造函数的上限大小为2 ^ 16字节,类静态初始化代码表示为具有时髦名称的特殊方法.

UPDATE

不幸的是,如果推得太远,你的自我答案解决方案仍会遇到不同的64K限制.拆分initialize()方法会绕过方法大小限制,但类常量池中的条目数有64K限制.每个String文字都需要一个常量池条目.


Tho*_*nin 5

这不是一个简单的解决方案,但您可以尝试...修补Java编译器.

当你编写一个时enum,Java编译器生成一个扩展的java.lang.Enum类(如果有特定于常量的方法,可能会有几个类).该类有一些(隐藏的)静态字段,在字节码级别,使用特殊<clinit>()方法(JVM在首次使用该类时调用)初始化.与任何其他方法一样,该方法的代码<clinit>()限制为65535字节.每个常量在<clinit>()字节码中贡献大约20到22个字节(如果有特定于常量的构造函数则更多),因此您在大约3000个枚举常量处达到极限.

现在这个<clinit>()方法有一个有趣的名字,但它没什么特别的; 它可以调用其他方法.Java编译器可以将猛犸象<clinit>()分成几个隐藏的子方法,<clinit>()然后一个接一个地调用.Java编译器目前不这样做,但理论上可以.任何JRE都可以处理结果.

或者,综合合成你的枚举类,从专用程序生成字节码,可能是用Java编写的.从本质上讲,这就像为特定目标编写自己的专用编译器并使用自己的源语法.该BCEL图书馆可能有帮助.

请注意,还有其他限制可能会对您产生影响.对于每个枚举常量,静态代码(中的一个<clinit>())使用两个"常量",它们是在编译类的"常量池"部分中聚合的内部值.这两个值是常量名称(作为字符串)和生成的静态字段引用.65536个常量池条目存在硬限制(索引位于16位),因此不超过32000个枚举常量.修补后的Java编译器可以通过生成几个隐藏类来绕过该限制,每个类都有自己的常量池.更严格的限制是静态字段的数量:每个枚举常量变为"enum"类中的静态字段,并且类中不能超过65535个字段(静态或非静态).

  • 我可能一直不清楚.我的消息的_whole point_可以在保持字节码与JVM期望的完全兼容的同时完成. (2认同)

Dan*_*ner 0

我最初的想法是使用 @Enumerated 注释来映射 Enum。这看起来像下面的例子:

@Enumerated(STRING)
private DescriptorEnum descriptor;
Run Code Online (Sandbox Code Playgroud)

数据库将有一个名为 DESCRIPTOR 的 varchar 类型列,Hibernate(在我的例子中)会将字符串映射到枚举。

但我有 65k 的限制(参见问题),这对我来说太小了。但我找到了解决方案。看一下下面的例子:

public final class Descriptor {
    public final String acronym;
    private static final Hashtable<String, Descriptor> all = new Hashtable<String, Descriptor>();
    static {
        initialize();
    }
    private static void initialize() {
        new Descriptor("example001");
        new Descriptor("example002");
        new Descriptor("example003");
    }
    private Descriptor(String acronym) {
        this.acronym = acronym;
        if (all.contains(this.acronym)) {
            throw new RuntimeException("duplicate acronym: " + this.acronym);
        }
        all.put(this.acronym, this);
    }
    public static Descriptor valueOf(String acronym) {
        return all.get(acronym);
    }
    public String value() {
        return this.acronym;
    }
}
Run Code Online (Sandbox Code Playgroud)

该描述符类模拟典型枚举的用法。但现在我可以将initialize() 方法拆分为多个方法,以解决方法也存在的65k 限制。枚举不允许将初始化分成几个块 - 我的类允许。

现在我必须使用稍微不同的映射:

@Column(name = "DESCRIPTOR")
private String                  descriptorAcronym       = null;
private transient Descriptor    descriptor              = null;
public Descriptor getDescriptor() {
    return descriptor;
}
public void setDescriptor(Descriptor desc) {
    this.descriptor = desc;
    this.descriptorAcronym = desc != null ? desc.acronym : null;
}
public String getDescriptorAcronym() {
    return descriptorAcronym;
}
public void setDescriptorAcronym(String desc) {
    this.descriptorAcronym = desc;
    this.descriptor = desc != null ? Descriptor.valueOf(desc) : null;
}
@PostLoad
private void syncDescriptor() {
    this.descriptor = this.descriptorAcronym != null ? Descriptor.valueOf(this.descriptorAcronym) : null;
}
Run Code Online (Sandbox Code Playgroud)

这样,在大多数情况下我可以像使用枚举一样使用该类。这有点棘手......但似乎有效。感谢您的所有意见,最终使我找到了该解决方案。