枚举超过静态初始化程序的65535字节限制...最好做什么?

时间:2010-03-30 15:28:50

标签: java compiler-construction enums limit

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

这是我的错误:静态初始值设定项的代码超出65535字节限制

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

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

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

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

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

5 个答案:

答案 0 :(得分:8)

简单。请勿使用enum。你不能。它不起作用。

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

此方法的另一个优点是您可以修改“描述符”映射,而无需修改程序的源代码。


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

<强>更新

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

答案 1 :(得分: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个字段(静态或非静态)。

答案 2 :(得分:3)

您可以在他的“Effective Java”一书的第一版中尝试Joshua Bloch所描述的typesafe enum pattern

我们的想法是创建一个带有私有构造函数的类。在这个类中,你可以定义你想要的任何数字的静态实例 - 就像在Java枚举中一样。

我不知道Java语言中静态成员的数量是否有限制。

答案 3 :(得分:0)

您可以尝试在顶级类中嵌套静态内部类

答案 4 :(得分:0)

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

@Enumerated(STRING)
private DescriptorEnum descriptor;

数据库将有一个名为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;
    }
}

此描述符类模拟典型枚举的用法。但是现在我能够将initialize()方法拆分成几个可以解决方法存在的65k限制的方法。 Enum不允许将初始化分成几个块 - 我的班级。

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

@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;
}

这种方式在大多数情况下我可以像Enum一样使用类。这有点棘手......但它似乎有效。感谢最终引导我解决的所有输入。