Enum Java et fichiers de classe supplémentaires

J’ai remarqué que les enums introduisent de nombreux fichiers de classes supplémentaires (classe $ 1) après compilation de la taille totale. Il semble être attaché à chaque classe qui utilise même un enum, et ceux-ci sont souvent dupliqués.

Pourquoi cela se produit-il et existe-t-il un moyen de prévenir cela sans supprimer le énumération?

(La raison de la question, c’est que l’espace est une priorité pour moi)

MODIFIER

En approfondissant le problème, le Javac 1.6 de Sun crée une classe synthétique supplémentaire chaque fois que vous utilisez un commutateur sur un Enum . Il utilise une sorte de SwitchMap. Ce site contient plus d’informations et vous explique comment parsingr ce que fait Javac.

Un fichier physique supplémentaire semble un prix élevé à payer chaque fois que vous utilisez un commutateur sur un enum!

Fait intéressant, le compilateur d’Eclipe ne produit pas ces fichiers supplémentaires. Je me demande si la seule solution est de changer de compilateur?

J’étais juste mordu par ce comportement et cette question a été révélée lors de la recherche sur Google. Je pensais partager le peu d’informations supplémentaires que j’ai trouvées.

javac 1.5 et 1.6 créent une classe synthétique supplémentaire chaque fois que vous utilisez un commutateur sur un enum. La classe contient une soi-disant “carte de commutation” qui mappe les indices enum pour changer les numéros de saut de table. Ce qui est important, la classe synthétique est créée pour la classe dans laquelle le commutateur se produit, pas la classe enum.

Voici un exemple de ce qui est généré:

EnumClass.java

 public enum EnumClass { VALUE1, VALUE2, VALUE3 } 

EnumUser.java

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

Synthétique 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; }; } 

Cette carte de commutateur est ensuite utilisée pour générer un index pour une instruction JVM de tableswitch lookupswitch ou de tableswitch . Il convertit chaque valeur en en un index correspondant de 1 à [nombre de cas de commutation].

EnumUser.class

 public java.lang.Ssortingng 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; //Ssortingng value 1 38: areturn 39: ldc #5; //Ssortingng value 3 41: areturn 42: ldc #6; //Ssortingng other 44: areturn 

tableswitch est utilisé s’il y a trois commutateurs ou plus lorsqu’il effectue une recherche à temps constant plus efficace par rapport à la recherche linéaire de lookupswitch . Techniquement parlant, javac pourrait omettre toute cette affaire avec la carte de commutation synthétique quand il utilise le lookupswitch .

Spéculation: Je n’ai pas le compilateur d’Eclipse à scope de main pour tester avec mais j’imagine qu’il ne dérange pas avec une classe synthétique et utilise simplement lookupswitch . Ou peut-être cela nécessite-t-il plus de boîtiers de commutateurs que le demandeur initial a testé avec avant “ugprades” à la tableswitch de tableswitch .

Les fichiers $ 1 etc. se produisent lorsque vous utilisez la fonctionnalité “implémentation de la méthode par instance” des énumérations de Java, comme ceci:

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

Ce qui précède va créer trois fichiers de classe, un pour la classe enum de base et un pour chacun des deux implémentations de foo ().

Au niveau du bytecode, les énumérations ne sont que des classes et, pour que chaque instance d’énumération implémente une méthode différemment, il doit y avoir une classe différente pour chaque instance.

Cependant, cela ne tient pas compte des fichiers de classe supplémentaires générés pour les utilisateurs de l’énumération, et je pense que ceux-ci ne sont que le résultat de classes anonymes et n’ont rien à voir avec les énumérations.

Ainsi, pour éviter de générer de tels fichiers de classe supplémentaires, n’utilisez pas les implémentations de méthodes par instance. Dans les cas tels que ci-dessus où les méthodes renvoient des constantes, vous pouvez utiliser un champ final public dans un constructeur (ou un champ privé avec un getter public si vous préférez). Si vous avez vraiment besoin de méthodes avec une logique différente pour différentes instances d’énumération, alors vous ne pouvez pas éviter les classes supplémentaires, mais je considère cela comme une fonctionnalité plutôt exotique et rarement nécessaire.

Je crois que cela est fait pour empêcher les commutateurs de se briser si l’ordre de l’énumération est modifié, tout en ne recompilant pas la classe avec le commutateur. Considérons le cas suivant:

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

Sans la carte de commutateur, foo() traduirait grossièrement en:

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

Puisque les instructions de cas doivent être des constantes de compilation (par exemple, pas d’appels de méthode). Dans ce cas, si l’ordre de A est changé, foo() imprimera “One” pour TWO et vice versa.

En Java, les énumérations ne sont en réalité que des classes avec un peu de sucre syntaxique.

Ainsi, chaque fois que vous définissez une nouvelle énumération, le compilateur Java crée un fichier de classe correspondant à vos besoins. (Peu importe la simplicité de l’énumération).

Pas moyen de contourner cela, les autres n’utilisant pas les énumérations.

Si l’espace est une prime, vous pouvez toujours utiliser les constantes à la place.

autant que je sache, étant donné une énumération nommée Operation vous obtiendrez des fichiers de classe supplémentaires, à l’exception de la classe visible Operation.class , et une valeur par enum, si vous utilisez une abstract method comme celle-ci:

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