Подтвердить что ты не робот

Enum exeeding предел 65535 байт статического инициализатора... что лучше всего сделать?

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

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

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

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

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

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

Любые идеи, как обрабатывать этот случай?

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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


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

UPDATE

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

Ответ 3

Это не простое решение, но вы можете попробовать... исправить компилятор 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 бит), поэтому не более чем на 32000 констант перечисления. Зашифрованный Java-компилятор мог обойти этот предел, создав несколько скрытых классов, каждый из которых имеет свой собственный постоянный пул. Более жестким ограничением является количество статических полей: каждая константа перечисления становится статическим полем в классе "enum", и не может быть не более 65535 полей (статических или нет) в классе.

Ответ 4

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

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

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

Ответ 5

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