Enum Java e arquivos de class adicionais

Eu tenho notado enums introduzir muitos arquivos de class adicionais (Classe $ 1) após a compilation inchaço do tamanho total. Parece estar ligado a todas as classs que usam um enum, e estas são frequentemente duplicadas.

Por que isso ocorre e existe uma maneira de evitar isso sem remover o enum.

(O motivo da pergunta é que o espaço é um prêmio para mim)

EDITAR

Ao investigar mais a questão, o Javac 1.6 da Sun cria uma class sintética adicional toda vez que você usa um switch em um Enum . Ele usa algum tipo de SwitchMap. Este site tem mais algumas informações e aqui informa como analisar o que o Javac está fazendo.

Um arquivo físico adicional parece ser um preço alto a ser pago toda vez que você usar um switch em um enum!

Curiosamente, o compilador do Eclipe não produz esses arquivos adicionais. Gostaria de saber se a única solução é trocar de compiladores?

Eu fui mordido por esse comportamento e essa questão apareceu quando pesquisando. Eu pensei em compartilhar o pouco de informação extra que descobri.

O javac 1.5 e o 1.6 criam uma class sintética adicional cada vez que você usa um switch em um enum. A class contém um chamado “switch map” que mapeia os índices de enumeração para alternar os números de salto de tabela. Importante, a class sintética é criada para a class na qual a opção ocorre, não a class enum.

Veja um exemplo do que é gerado:

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 sintético $ 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; }; } 

Este mapa de switch é então usado para gerar um índice para uma lookupswitch tableswitch ou tableswitch instrução JVM. Ele converte cada valor de enum em um índice correspondente de 1 para [number of switch cases].

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 é usado se houver três ou mais casos de comutação, uma vez que executa uma pesquisa de tempo constante mais eficiente em comparação com a busca linear do lookupswitch . Tecnicamente falando, o javac poderia omitir todo esse negócio com o mapa sintético do switch quando ele usa o lookupswitch .

Especulação: Eu não tenho o compilador do Eclipse para testar, mas imagino que ele não se importe com uma class sintética e simplesmente use lookupswitch . Ou talvez ele exija mais casos de switch do que o originador original testado antes de “subir” para a tableswitch .

Os arquivos $ 1 etc. ocorrem quando você usa o recurso “implementação por método de instância por instância” das enumerações do Java, assim:

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

O acima irá criar três arquivos de class, um para a class enum base e um para YEA e NAY para manter as diferentes implementações de foo ().

No nível de bytecode, enums são apenas classs e, para que cada instância de enum implemente um método de maneira diferente, é necessário que exista uma class diferente para cada instância,

No entanto, isso não conta para arquivos de class adicionais gerados para usuários do enum e suspeito que esses são apenas o resultado de classs anônimas e não têm nada a ver com enums.

Assim, para evitar que esses arquivos de classs extras sejam gerados, não use implementações de methods por instância. Em casos como o acima, em que os methods retornam constantes, você pode usar um campo final público definido em um construtor (ou um campo privado com um getter público, se preferir). Se você realmente precisa de methods com lógica diferente para instâncias diferentes de enum, então você não pode evitar as classs extras, mas eu consideraria um recurso bastante exótico e raramente necessário.

Eu acredito que isso é feito para evitar que os switches quebrem se a ordem do enum for alterada, enquanto não recompilar a class com o switch. Considere o seguinte caso:

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

Sem o mapa do switch, foo() seria traduzido aproximadamente para:

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

Como as declarações de caso devem ser constantes de tempo de compilation (por exemplo, não chamadas de método). Nesse caso, se a ordem de A for trocada, foo() imprimiria “One” para TWO e vice-versa.

Em Java, as enumerações são realmente apenas classs com algum açúcar sintático ativado.

Portanto, sempre que você definir uma nova Enumeração, o compilador Java criará um arquivo de Classe correspondente para você. (Não importa quão simples seja a enumeração).

Não há maneira de contornar isso, outras não usando Enumerações.

Se o espaço é um prêmio, você pode sempre usar Constantes.

Até onde eu sei, dado um enum chamado Operation você obterá arquivos de class adicionais, excluindo o óbvio Operation.class , e um por valor enum, se você estiver usando um abstract method como este:

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