Java enum y archivos de clase adicionales

Noté que las enums introducen muchos archivos de clase adicionales (Clase $ 1) después de que la comstackción hincha el tamaño total. Parece estar unido a cada clase que incluso utiliza una enumeración, y estos a menudo se duplican.

¿Por qué ocurre esto y hay una manera de evitar esto sin eliminar la enumeración?

(La razón de la pregunta es que el espacio es un premio para mí)

EDITAR

Al investigar más el tema, Sun’s Javac 1.6 crea una clase sintética adicional cada vez que utiliza un interruptor en un Enum . Utiliza algún tipo de SwitchMap. Este sitio tiene algo más de información, y aquí le dice cómo analizar qué está haciendo Javac.

¡Un archivo físico adicional parece ser un precio elevado para pagar cada vez que utiliza un interruptor en una enumeración!

Curiosamente, el comstackdor de Eclipe no produce estos archivos adicionales. Me pregunto si la única solución es cambiar los comstackdores.

Solo estaba un poco por este comportamiento y esta pregunta apareció cuando busqué en Google. Pensé que compartiría la poca información extra que descubrí.

javac 1.5 y 1.6 crean una clase sintética adicional cada vez que utilizas un interruptor en una enumeración. La clase contiene un llamado “mapa de conmutación” que mapea índices enum para cambiar los números de salto de tabla. Es importante destacar que la clase sintética se crea para la clase en la que se produce el cambio, no la clase enum.

Aquí hay un ejemplo de lo que se genera:

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

Este mapa de cambio se usa para generar un índice para una instrucción JVM lookupswitch o tableswitch . Convierte cada valor enum en un índice correspondiente de 1 a [número de cajas de interruptor].

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 se usa si hay tres o más cajas de conmutadores, ya que realiza una búsqueda de tiempo constante más eficiente que la búsqueda lineal de un buscador. Técnicamente, javac podría omitir todo este asunto con el mapa de conmutación sintético cuando usa el lookupswitch .

Especulación: no tengo el comstackdor de Eclipse a la mano para probarlo, pero me imagino que no se molesta con una clase sintética y simplemente usa el lookupswitch . O quizás requiera más cajas de conmutadores que las que probó el asker original antes de “cambiar” a tableswitch de tableswitch .

Los archivos de $ 1, etc. se producen cuando utiliza la función “implementación de método por instancia” de las enumeraciones de Java, como esta:

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

Lo anterior creará tres archivos de clase, uno para la clase enum base y otro para YEA y NAY para contener las diferentes implementaciones de foo ().

En el nivel de bytecode, las enumeraciones son solo clases, y para que cada instancia enum implemente un método de manera diferente, debe haber una clase diferente para cada instancia,

Sin embargo, esto no cuenta para los archivos de clase adicionales generados por los usuarios de la enumeración, y sospecho que esos son solo el resultado de clases anónimas y no tienen nada que ver con las enumeraciones.

Por lo tanto, para evitar que se generen dichos archivos de clase extra, no use implementaciones de métodos por instancia. En casos como el anterior donde los métodos devuelven constantes, puede usar un campo final público configurado en un constructor (o un campo privado con un getter público, si lo prefiere). Si realmente necesitas métodos con lógica diferente para diferentes instancias de enum, entonces no puedes evitar las clases adicionales, pero lo consideraría una característica bastante exótica y rara vez necesaria.

Creo que esto se hace para evitar que los switches se rompan si se cambia el orden de la enumeración, mientras que no se vuelve a comstackr la clase con el conmutador. Considere el siguiente 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; } } } 

Sin el mapa de cambio, foo() se traduciría aproximadamente a:

  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 las declaraciones de casos deben ser constantes de tiempo de comstackción (p. Ej., No llamadas de método). En este caso, si se cambia el orden de A , foo() imprimirá “Uno” para DOS, y viceversa.

En Java, las enumeraciones son realmente solo clases con algo de azúcar sintáctico lanzado.

Por lo tanto, cada vez que defina una nueva enumeración, el comstackdor de Java creará un archivo de clase correspondiente para usted. (No importa cuán simple sea la enumeración).

No hay forma de evitar esto, otros no usan Enumeraciones.

Si el espacio es una prima, siempre puede usar Constantes en su lugar.

hasta donde yo sé, dada una enumeración llamada Operation , obtendrás archivos de clase adicionales, excluyendo la obvia Operation.class , y una por valor enum, si estás usando 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); }