Перечисление Java и дополнительные файлы классов - PullRequest
61 голосов
/ 02 декабря 2009

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

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

(Причиной для вопроса является то, что пространство для меня дороже)

EDIT

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

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

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

Ответы [ 5 ]

57 голосов
/ 01 декабря 2010

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

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

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

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

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

Эта карта переключателей затем используется для генерации индекса для инструкции JVM lookupswitch или tableswitch. Он преобразует каждое значение перечисления в соответствующий индекс от 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. Или, возможно, для этого требуется больше переключателей, чем было проверено с оригинальным аскером, прежде чем он «вырастет» до tableswitch.

6 голосов
/ 02 декабря 2009

Файлы $ 1 и т. Д. Появляются, когда вы используете функцию «реализации метода для каждого экземпляра» перечислений Java, например:

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

    public abstract boolean foo();
}

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

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

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

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

5 голосов
/ 24 апреля 2012

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

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() выведет «Один» для ДВУХ и наоборот.

1 голос
/ 02 декабря 2009

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

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

Нет способа обойти это, кроме как без использования Перечислений.

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

0 голосов
/ 02 декабря 2009

Насколько я знаю, при заданном перечислении с именем Operation вы получите дополнительные файлы классов, исключая очевидное Operation.class, и по одному на каждое перечисление, если вы используете 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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...