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

Имеет ли класс Enum, содержащий 2000 + 1 Enum Constants, предел?

Следующий код не работает с NullPointerException в главном (map==null). Проблема возникает только в том случае, если я определяю 2001 или более констант Enum, 2000 работает нормально.

Почему статический блок кода не выполняется?

Удаляем ли мы любой тихий предел компилятора (никаких предупреждений, ошибок) или JVM?

Скомпилированный файл класса превышает 172 КБ,

import java.util.HashMap;

public enum EnumTest {
    E(1),E(2),...,E(2001);

    private static HashMap<Integer, EnumTest>   map = new HashMap<Integer, EnumTest>();

    static {

        for ( EnumTest f : EnumTest.values() ) {
            map.put( (int) f.id, f );
        }
    }
    short id;

    private EnumTest(int id) {
        this.id = (short) id;
    };

    public short getId() {
        return id;
    }

    public static final EnumTest fromInt(int id) {
        EnumTest e = map.get( id );
        if ( e != null ) {
            return e;
        }
        throw new IllegalArgumentException( "" + id );
    }

    public static void main(String[] args) {
        System.out.println( "size:" + map.size() );
    }
}

Среда выполнения:

java version "1.7.0_01"
Java(TM) SE Runtime Environment (build 1.7.0_01-b08)
Java HotSpot(TM) 64-Bit Server VM (build 21.1-b02, mixed mode)

также происходит с:

java version "1.6.0_32" Java(TM) SE Runtime Environment (build
1.6.0_32-b05) Java HotSpot(TM) Client VM (build 20.7-b02, mixed mode, sharing)
4b9b3361

Ответ 1

Эти проблемы возникают из-за того, что некоторый (часто генерируемый компилятором) код инициализатора превышает 65536 байт байтового кода. Один метод не может содержать больше, чем должно быть выполнено много байтов байтового кода (из-за ограничений в формате файла классов).

Общим источником таких проблем являются такие большие массивы, как:

byte someBytes = { 1, 2, 3, ..., someBigValue };

Проблема в том, что такие поля фактически инициализируются операторами присваивания someBigValue в сгенерированном инициализаторе (конструктор или статический инициализатор).

Значения Enum фактически инициализируются аналогичным образом.

С учетом следующего класса перечисления:

public enum Foo {
  CONSTANT(1);

  private Foo(int i) {
  }
}

Мы смотрим на вывод javap -v и видим следующий код:

  static {};
    flags: ACC_STATIC
    Code:
      stack=5, locals=0, args_size=0
         0: new           #4                  // class Foo
         3: dup
         4: ldc           #7                  // String CONSTANT
         6: iconst_0
         7: iconst_1
         8: invokespecial #8                  // Method "<init>":(Ljava/lang/String;II)V
        11: putstatic     #9                  // Field CONSTANT:LFoo;
        14: iconst_1
        15: anewarray     #4                  // class Foo
        18: dup
        19: iconst_0
        20: getstatic     #9                  // Field CONSTANT:LFoo;
        23: aastore
        24: putstatic     #1                  // Field $VALUES:[LFoo;
        27: return

Как вы видите, существует довольно много операций байт-кода, которые обрабатывают экземпляр CONSTANT с правильными значениями. Если у вас много таких значений перечисления, размер этого статического блока инициализатора может легко превышать 64k байт кода и, таким образом, сделать класс несовместимым.

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

В качестве альтернативы вы можете попробовать расщепить свой enum на несколько перечислений, связанных посредством реализации общего интерфейса. Перечисления могут быть сгруппированы по области/намерению/категории/...:

public interface MessageType {
  int getId();
}

public enum ConnectionMessage implements MessageType {
  INIT_CONNECTION(1),
  LOGIN(2),
  LOGOUT(3),
  CLOSE_CONNECTION(4);

  // getId code, constructor, ...
}

public enum FrobnicationMessage implements MessageType {
  FROBNICATE_FOO(5),
  FROBNICATE_BAR(6),
  DEFROB_FOO(7),
  DEFROB_BAR(8),
  ...

  // getId code, constructor, ...
}

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

Ответ 2

Я подозреваю, что вы должны видеть ошибку при компиляции

error: code too large

Возможно, ваша версия компилятора имеет ошибку и не показывает этого.

Когда я создаю 2500 значений перечислений, это происходит с ошибкой, но с 2400 значениями перечисления он работает правильно.

Существует предел в 64 Кбайта для байтового кода любого метода, и перечисления инициализируются в одном методе для блока статического инициализатора.

Проблема заключается в том, что многие инструкции байтового кода используют смещение байта как 16-битное значение, которое помещает это ограничение на весь метод (даже если в конце метода нет такой команды)

javac недостаточно умен, чтобы разбить блок статического инициализатора на несколько под-методов, но затем снова имея тысячи enums, предложите вам сделать то, что требуется другим способом.