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

Перечисление Java и дополнительные файлы классов

Я заметил, что enums вводят много дополнительных файлов классов (класс $ 1) после компиляции, раздувающей общий размер. Кажется, он привязан к каждому классу, который даже использует перечисление, и они часто дублируются.

Почему это происходит, и есть ли способ предотвратить это, не удаляя перечисление.

(Причина для вопроса - это пространство для меня на премию)

РЕДАКТИРОВАТЬ

Изучая проблему, Sun Javac 1.6 создает дополнительный синтетический класс каждый раз, когда вы используете переключатель Enum. Он использует какой-то SwitchMap. На этом сайте есть дополнительная информация, и здесь рассказывается, как анализировать, что делает Javac.

Дополнительный физический файл кажется высокой ценой, которую нужно платить каждый раз, когда вы используете переключатель на перечислении!

Интересно, что компилятор Eclipe не создает эти дополнительные файлы. Интересно, единственное решение - переключить компиляторы?

4b9b3361

Ответ 1

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

javac 1.5 и 1.6 создают дополнительный синтетический класс каждый раз, когда вы используете переключатель на перечислении. Класс содержит так называемую "карту коммутаторов", которая отображает индексы перечисления для переключения чисел перехода таблицы. Важно отметить, что синтетический класс создается для класса, в котором находится коммутатор, а не класса перечисления.

Вот пример того, что генерируется:

EnumClass.java

public enum EnumClass { VALUE1, VALUE2, VALUE3 }

EnumUser.java

public class EnumUser {
    public String getName(EnumClass value) {
        switch (value) {
            case VALUE1: return "value 1";
            // No VALUE2 case.
            case VALUE3: return "value 3";
            default:     return "other";
        }
    }
}

Синтетический список EnumUser $1.class

class EnumUser$1 {
    static final int[] $SwitchMap$EnumClass = new int[EnumClass.values().length];

    static {
        $SwitchMap$EnumClass[EnumClass.VALUE1.ordinal()] = 1;
        $SwitchMap$EnumClass[EnumClass.VALUE3.ordinal()] = 2;
    };
}

Эта карта коммутатора затем используется для создания индекса для команды lookupswitch или tableswitch JVM. Он преобразует каждое значение перечисления в соответствующий индекс от 1 до [количество случаев переключения].

EnumUser.class

public java.lang.String getName(EnumClass);
  Code:
   0:   getstatic       #2; //Field EnumUser$1.$SwitchMap$EnumClass:[I
   3:   aload_1
   4:   invokevirtual   #3; //Method EnumClass.ordinal:()I
   7:   iaload
   8:   lookupswitch{ //2
                1: 36;
                2: 39;
                default: 42 }
   36:  ldc     #4; //String value 1
   38:  areturn
   39:  ldc     #5; //String value 3
   41:  areturn
   42:  ldc     #6; //String other
   44:  areturn

tableswitch используется, если есть три или более случая коммутатора, поскольку он выполняет более эффективный поиск по постоянному времени и линейный поиск lookupswitch. Технически говоря, javac может опустить весь этот бизнес с синтетической картой коммутатора, когда он использует lookupswitch.

Спекуляция: У меня нет компилятора Eclipse для проверки, но я думаю, что он не беспокоит синтетический класс и просто использует lookupswitch. Или, возможно, для этого требуется больше случаев переключения, чем исходный опросанный, перед тем, как он "ugprades" до tableswitch.

Ответ 2

Файлы $1 и т.д. возникают, когда вы используете функцию реализации метода per-instance для перечислений Java, например:

public enum Foo{
    YEA{
        public void foo(){ return true };
    },
    NAY{
        public void foo(){ return false };
    };

    public abstract boolean foo();
}

Вышеупомянутое создаст три файла классов: один для базового класса перечисления и один для YEA и NAY для хранения различных реализаций foo().

На уровне байт-кода перечисления - это просто классы, и для того, чтобы каждый экземпляр enum реализовал метод по-разному, для каждого экземпляра должен быть другой класс,

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

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

Ответ 3

Я считаю, что это сделано, чтобы предотвратить переключение коммутаторов при изменении порядка перечисления, не перекомпилируя класс с помощью коммутатора. Рассмотрим следующий случай:

enum A{
    ONE, //ordinal 0
    TWO; //ordinal 1
}
class B{
     void foo(A a){
         switch(a){
              case ONE:
                   System.out.println("One");
                   break;
              case TWO:
                   System.out.println("Two");
                   break;
         }
     }
}

Без карты коммутатора foo() будет грубо перевести на:

 void foo(A a){
         switch(a.ordinal()){
              case 0: //ONE.ordinal()
                   System.out.println("One");
                   break;
              case 1: //TWO.ordinal()
                   System.out.println("Two");
                   break;
         }
     }

Поскольку операторы case должны быть константами времени компиляции (например, не вызовы методов). В этом случае, если порядок A переключается, foo() будет печатать "One" для TWO и наоборот.

Ответ 4

В Java перечисления на самом деле являются просто классами с синтаксическим сахаром.

Поэтому в любое время, когда вы определяете новую Enumeration, компилятор Java создаст для вас соответствующий файл класса. (Независимо от того, насколько простым является перечисление).

Невозможно обойти это, иначе, не используя Enumerations.

Если пространство является премией, вы всегда можете просто использовать константы.

Ответ 5

насколько я знаю, с учетом перечисления с именем Operation вы получите дополнительные файлы классов, за исключением очевидных Operation.class и one per enum value, если вы используете abstract method, как этот:

enum Operation {

   ADD {
      double op(double a, double b) { 
          return a + b;
      }
   },

   SUB {
      double op(double a, double b) { 
          return a - b;
      }
   };

   abstract double op(double a, double b);
}