what - static enum java
Java enum y archivos de clase adicionales (5)
Noté que las enums
introducen muchos archivos de clase adicionales (Clase $ 1) después de que la compilació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. This sitio tiene algo más de información, y here 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 compilador de Eclipe no produce estos archivos adicionales. Me pregunto si la única solución es cambiar los compiladores.
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 compilar 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 compilació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 compilador 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.
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.
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 compilador 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
.
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);
}