Enum, превышающий ограничение в 65535 байтов статического инициализатора ... что лучше сделать? - PullRequest
18 голосов
/ 30 марта 2010

Я запустил довольно большое Enum из так называемых Descriptors , которые я хотел использовать в качестве списка ссылок в моей модели. Но теперь я впервые столкнулся с ограничением компилятора / виртуальной машины и поэтому ищу лучшее решение для этого.

Вот моя ошибка: Код для статического инициализатора превышает ограничение 65535 байт

Понятно, откуда это взялось - у моего Enum просто много элементов. Но мне нужны эти элементы - нет способа уменьшить этот набор.

Изначально я планировал использовать один Enum, потому что я хочу убедиться, что все элементы в Enum уникальны. Он используется в постоянном контексте Hibernate , где ссылка на Enum хранится в базе данных как значение String. Так что это должно быть уникальным!

Содержание моего Enum можно разделить на несколько групп элементов, принадлежащих друг другу. Но разделение Enum лишит меня уникальной безопасности во время компиляции. Или это может быть достигнуто с помощью нескольких перечислений каким-либо образом?

Моя единственная текущая идея - определить некоторый Интерфейс с именем Дескриптор и кодировать несколько Enums, реализующих его. Таким образом, я надеюсь, что смогу использовать отображение Hibernate Enum, как если бы это был один Enum. Но я даже не уверен, сработает ли это. И я теряю уникальную безопасность.

Есть идеи, как обращаться с этим делом?

Ответы [ 5 ]

8 голосов
/ 30 марта 2010

Simple. Не используйте enum для этого. Ты не можешь Это не сработает.

Скорее всего, ваш исходный код явно не ссылается на многие значения перечисления. Скорее, вы используете enum как удобный способ сопоставления уникальных экземпляров объектов и имен строк. Так что просто замените тип enum на тип, который явно управляет отображением, инициализируя его чтением из файла или базы данных. Если вы все сделаете правильно, вы получите вычислительные свойства и безопасность типов перечисления. Единственное, что вы теряете, это синтаксический сахар ... и статика.

Этот подход будет иметь дополнительное преимущество, заключающееся в том, что вы можете изменять отображение «дескрипторов» без изменения исходного кода вашей программы.


Кстати, ограничение, с которым вы сталкиваетесь, налагается форматом файла класса JVM. У метода или конструктора верхний предел размера составляет 2 ^ 16 байт, а код статической инициализации классов представлен в виде специального метода со смешным именем.

UPDATE

К сожалению, ваше решение для самостоятельного ответа будет по-прежнему сталкиваться с другим пределом в 64 КБ ... если оно слишком велико. Разделение метода initialize() позволяет обойти ограничение размера метода, но есть и ограничение в 64 КБ для числа записей в пуле констант классов. Каждый строковый литерал требует постоянной записи пула.

5 голосов
/ 30 марта 2010

Это не простое решение, но вы можете попытаться ... пропатчить компилятор Java.

Когда вы пишете enum, компилятор Java генерирует класс, который расширяет java.lang.Enum (возможно, несколько классов, если существуют методы, специфичные для констант). Класс имеет некоторые (скрытые) статические поля, которые на уровне байт-кода инициализируются специальным методом <clinit>() (который JVM вызывает при первом использовании класса). Как и любой другой метод, код для метода <clinit>() ограничен 65535 байтами. Каждая константа вносит примерно 20-22 байта в байт-код <clinit>() (больше, если есть конструкторы, специфичные для констант), поэтому вы достигнете предела примерно 3000 констант перечисления.

Теперь у метода <clinit>() есть забавное имя, но в нем нет ничего особенного; он может вызывать другие методы. Компилятор Java может разделить мамонта <clinit>() на несколько скрытых под-методов, которые <clinit>() будут вызывать один за другим. Компилятор Java в настоящее время не делает этого, но теоретически может. Результат будет обработан любым JRE.

В качестве альтернативы, вы можете синтезировать класс enum синтетически, генерируя байт-код из специальной программы, возможно, написанной на Java. По сути, это похоже на написание собственного специализированного компилятора для конкретной цели и использование собственного синтаксиса исходного кода. Библиотека BCEL может помочь.

Обратите внимание, что есть и другие ограничения, которые могут на вас повлиять. Для каждой константы перечисления в статическом коде (в <clinit>()) используются две «константы», которые являются внутренними значениями, агрегированными в части «пул констант» скомпилированного класса. Два значения - это имя константы (в виде строки) и результирующая ссылка на статическое поле. Существует жесткое ограничение на 65536 записей пула констант (индексы на 16 битах), поэтому не более 32 000 констант перечисления. Запатентованный компилятор Java может обойти это ограничение, создав несколько скрытых классов, каждый со своим собственным пулом констант. Более жесткое ограничение - количество статических полей: каждая константа перечисления становится статическим полем в классе «enum», и в классе может быть не более 65535 полей (статических или нет).

3 голосов
/ 30 марта 2010

Вы можете попробовать типизированный шаблон enum описано Джошуа Блохом в первом издании его книги "Эффективная Ява".

Идея состоит в том, чтобы создать класс с закрытым конструктором. Внутри этого класса вы определяете статические экземпляры любого числа, которое хотите - как в перечислении Java.

Я не знаю, существует ли ограничение на количество статических элементов в языке Java.

0 голосов
/ 31 марта 2010

Моя оригинальная идея состояла в том, чтобы отобразить Enum с помощью аннотации @Enumerated.Это выглядело бы как в следующем примере:

@Enumerated(STRING)
private DescriptorEnum descriptor;

База данных будет иметь столбец с именем DESCRIPTOR типа varchar, а Hibernate (в моем случае) отобразит строку в перечисление.

Но у меня есть тот предел в 65 КБ (см. Вопрос), который в моем случае мал.Но я нашел решение.Посмотрите на следующий пример:

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 () на несколько методов, работающих с пределом в 65 КБ, который также существует для методов.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 в большинстве случаев.Это немного сложно ... но, похоже, работает.Спасибо за весь вклад, который наконец привел меня к этому решению.

0 голосов
/ 30 марта 2010

Вы можете попробовать вложить статические внутренние классы в класс верхнего уровня

...