Java: Enum vs. Int

En utilisant des drapeaux en Java, j’ai vu deux approches principales. On utilise des valeurs int et une ligne d’instructions if-else. L’autre consiste à utiliser des énumérations et des instructions de changement de casse.

Je me demandais s’il y avait une différence en termes d’utilisation de la mémoire et de vitesse entre l’utilisation des énumérations et des ints pour les indicateurs?

Tant les ints que les enums peuvent utiliser à la fois le switch ou if-then-else, et l’utilisation de la mémoire est également minimale pour les deux, et la vitesse est similaire – il n’y a pas de différence significative entre les points que vous avez soulevés.

Cependant, la différence la plus importante est la vérification du type. Enums sont vérifiées, les ints ne le sont pas.

Considérez ce code:

 public class SomeClass { public static int RED = 1; public static int BLUE = 2; public static int YELLOW = 3; public static int GREEN = 3; // sic private int color; public void setColor(int color) { this.color = color; } } 

Bien que beaucoup de clients l’utiliseront correctement,

 new SomeClass().setColor(SomeClass.RED); 

Il n’y a rien qui les empêche d’écrire ceci:

 new SomeClass().setColor(999); 

L’utilisation du modèle public static final pose trois problèmes principaux:

  • Le problème se produit à l’ exécution , pas à la compilation , il sera donc plus coûteux de résoudre le problème et plus difficile à trouver.
  • Vous devez écrire du code pour gérer les mauvaises entrées – en général, une if-then-else avec une version finale else throw new IllegalArgumentException("Unknown color " + color); – encore cher
  • Rien n’empêche une collision de constantes – le code de classe ci-dessus sera compilé même si YELLOW et GREEN ont tous deux la même valeur 3

Si vous utilisez des enums , vous abordez tous ces problèmes:

  • Votre code ne sera pas compilé à moins que vous ne transmettiez des valeurs valides dans
  • Pas besoin de code spécial “mauvaise entrée” – le compilateur gère cela pour vous
  • Les valeurs enum sont uniques

Vous pouvez même utiliser Enums pour remplacer ces indicateurs combinés au niveau du bit comme int flags = FLAG_1 | FLAG_2; int flags = FLAG_1 | FLAG_2;

Au lieu de cela, vous pouvez utiliser un enumSet typeafe :

 Set flags = EnumSet.of(FlagEnum.FLAG_1, FlagEnum.FLAG_2); // then simply test with contains() if(flags.contains(FlagEnum.FLAG_1)) ... 

La documentation indique que ces classes sont optimisées en interne en tant que vecteurs de bits et que l’implémentation doit être suffisamment performante pour remplacer les indicateurs int.

L’utilisation de la mémoire et la vitesse ne sont pas les considérations importantes. Vous ne seriez pas en mesure de mesurer une différence de toute façon.

Je pense que les énumérations devraient être préférées lorsqu’elles s’appliquent, car elles soulignent le fait que les valeurs choisies vont de pair et comprennent un ensemble fermé. La lisibilité est également améliorée. Le code utilisant des énumérations est plus auto-documenté que les valeurs d’interstice égarées dispersées dans votre code.

Préférez les enums.

L’une des raisons pour lesquelles vous verrez du code en utilisant int flags au lieu d’un enum est que Java n’avait pas d’ enum jusqu’à Java 1.5

Donc, si vous regardez du code écrit à l’origine pour une ancienne version de Java, le modèle int était la seule option disponible.

Il y a un très petit nombre d’endroits où l’utilisation de drapeaux int est encore préférable dans le code Java moderne, mais dans la plupart des cas, vous devriez préférer utiliser un enum , en raison du type de sécurité et d’expressivité qu’ils offrent.

En termes d’efficacité, cela dépendra de la façon dont ils sont utilisés. La JVM gère les deux types de manière très efficace, mais la méthode int serait probablement légèrement plus efficace pour certains cas d’utilisation (car ils sont gérés comme des primitifs plutôt que comme des objects), mais dans d’autres cas, l’énumération serait plus efficace Il ne faut pas aller jeter un coup de boxe / unboxing).

Vous auriez du mal à trouver une situation dans laquelle la différence de rendement serait perceptible dans une application du monde réel , vous devriez donc prendre la décision en fonction de la qualité du code (lisibilité et sécurité), ce qui devrait vous conduire à utiliser un enum 99% du temps .

Gardez à l’esprit que les enums sont sûres et que vous ne pouvez pas mélanger les valeurs d’un enum à un autre. C’est une bonne raison de préférer les enums aux ints pour les drapeaux.

D’un autre côté, si vous utilisez ints pour vos constantes, vous pouvez mélanger les valeurs de constantes sans rapport, comme ceci:

 public static final int SUNDAY = 1; public static final int JANUARY = 1; ... // even though this works, it's a mistake: int firstMonth = SUNDAY; 

L’utilisation de la mémoire des enums sur les ints est négligeable, et le type de sécurité fourni rend la surcharge minimale acceptable.

Répondez à votre question: Non, après un temps négligeable de chargement de la classe Enum, la performance est la même.

Comme d’autres l’ont déclaré, les deux types peuvent être utilisés dans les instructions switch ou if else. En outre, comme d’autres l’ont déclaré, vous devriez privilégier Enums par rapport aux indicateurs, car ils ont été conçus pour remplacer ce modèle et offrent une sécurité accrue.

Cependant, il existe un meilleur modèle que vous considérez. Fournir n’importe quelle valeur à votre déclaration switch / si l’instruction était censée produire en tant que propriété.

Regardez ce lien: http://docs.oracle.com/javase/1.5.0/docs/guide/language/enums.html Notez le modèle fourni pour donner les masses et les rayons des planètes. Fournir la propriété de cette manière assure que vous n’oublierez pas de couvrir un cas si vous ajoutez un enum.

J’aime utiliser Enums lorsque cela est possible mais je devais calculer des millions de fichiers pour différents types de fichiers que j’avais définis dans un enum et je devais exécuter des changements de dizaines de millions de fois pour calculer la base de décalage. sur le type enum. J’ai effectué le test suivant:

 import java.util.Random; 

classe publique switchTest {public enum MyEnum {Value1, Value2, Value3, Value4, Value5};

 public static void main(Ssortingng[] args) { final Ssortingng s1 = "Value1"; final Ssortingng s2 = "Value2"; final Ssortingng s3 = "Value3"; final Ssortingng s4 = "Value4"; final Ssortingng s5 = "Value5"; Ssortingng[] ssortingngs = new Ssortingng[] { s1, s2, s3, s4, s5 }; Random r = new Random(); long l = 0; long t1 = System.currentTimeMillis(); for(int i = 0; i < 10_000_000; i++) { String s = strings[r.nextInt(5)]; switch(s) { case s1: // make sure the compiler can't optimize the switch out of existence by making the work of each case it does different l = r.nextInt(5); break; case s2: l = r.nextInt(10); break; case s3: l = r.nextInt(15); break; case s4: l = r.nextInt(20); break; case s5: l = r.nextInt(25); break; } } long t2 = System.currentTimeMillis(); for(int i = 0; i < 10_000_000; i++) { MyEnum e = MyEnum.values()[r.nextInt(5)]; switch(e) { case Value1: // make sure the compiler can't optimize the switch out of existence by making the work of each case it does different l = r.nextInt(5); break; case Value2: l = r.nextInt(10); break; case Value3: l = r.nextInt(15); break; case Value4: l = r.nextInt(20); break; case Value5: l = r.nextInt(25); break; } } long t3 = System.currentTimeMillis(); for(int i = 0; i < 10_000_000; i++) { int xx = r.nextInt(5); switch(xx) { case 1: // make sure the compiler can't optimize the switch out of existence by making the work of each case it does different l = r.nextInt(5); break; case 2: l = r.nextInt(10); break; case 3: l = r.nextInt(15); break; case 4: l = r.nextInt(20); break; case 5: l = r.nextInt(25); break; } } long t4 = System.currentTimeMillis(); System.out.println("strings:" + (t2 - t1)); System.out.println("enums :" + (t3 - t2)); System.out.println("ints :" + (t4 - t3)); } 

}

et a obtenu les résultats suivants:

cordes: 442

énumérations: 455

ints: 362

Donc, à partir de là, j'ai décidé que pour moi, les énumérations étaient suffisamment efficaces. Lorsque j'ai diminué le nombre de boucles à 1M à partir de 10M, la chaîne et les énumérations ont pris environ deux fois plus de temps que l'int, ce qui indique que l'utilisation de chaînes et d'énumérations pour la première fois par rapport à ints.

Oui, il y a une différence. Sous Java 64 bits modernes, les valeurs Enum sont essentiellement des pointeurs vers des objects et prennent soit 64 bits (opérations non compressées), soit utilisent un processeur supplémentaire (opérations compressées).

Mon test a révélé une dégradation des performances d’environ 10% pour les énumérations (1.8u25, AMD FX-4100): 13k ns vs 14k ns

Source de test ci-dessous:

 public class Test { public static enum Enum { ONE, TWO, THREE } static class CEnum { public Enum e; } static class CInt { public int i; } public static void main(Ssortingng[] args) { CEnum[] enums = new CEnum[8192]; CInt[] ints = new CInt[8192]; for (int i = 0 ; i < 8192 ; i++) { enums[i] = new CEnum(); ints[i] = new CInt(); ints[i].i = 1 + (i % 3); if (i % 3 == 0) { enums[i].e = Enum.ONE; } else if (i % 3 == 1) { enums[i].e = Enum.TWO; } else { enums[i].e = Enum.THREE; } } int k=0; //calculate something to prevent tests to be optimized out k+=test1(enums); k+=test1(enums); k+=test1(enums); k+=test1(enums); k+=test1(enums); k+=test1(enums); k+=test1(enums); k+=test1(enums); k+=test1(enums); k+=test1(enums); System.out.println(); k+=test2(ints); k+=test2(ints); k+=test2(ints); k+=test2(ints); k+=test2(ints); k+=test2(ints); k+=test2(ints); k+=test2(ints); k+=test2(ints); k+=test2(ints); System.out.println(k); } private static int test2(CInt[] ints) { long t; int k = 0; for (int i = 0 ; i < 1000 ; i++) { k+=test(ints); } t = System.nanoTime(); k+=test(ints); System.out.println((System.nanoTime() - t)/100 + "ns"); return k; } private static int test1(CEnum[] enums) { int k = 0; for (int i = 0 ; i < 1000 ; i++) { k+=test(enums); } long t = System.nanoTime(); k+=test(enums); System.out.println((System.nanoTime() - t)/100 + "ns"); return k; } private static int test(CEnum[] enums) { int i1 = 0; int i2 = 0; int i3 = 0; for (int j = 100 ; j != 0 ; --j) for (int i = 0 ; i < 8192 ; i++) { CEnum c = enums[i]; if (ce == Enum.ONE) { i1++; } else if (ce == Enum.TWO) { i2++; } else { i3++; } } return i1 + i2*2 + i3*3; } private static int test(CInt[] enums) { int i1 = 0; int i2 = 0; int i3 = 0; for (int j = 100 ; j != 0 ; --j) for (int i = 0 ; i < 8192 ; i++) { CInt c = enums[i]; if (ci == 1) { i1++; } else if (ci == 2) { i2++; } else { i3++; } } return i1 + i2*2 + i3*3; } } 

Même si cette question est ancienne, j’aimerais souligner ce que vous ne pouvez pas faire avec

 public interface AtsortingbuteProcessor { public void process(AtsortingbuteLexer atsortingbuteLexer, char c); } public enum ParseArrayEnd implements AtsortingbuteProcessor { State1{ public void process(AtsortingbuteLexer atsortingbuteLexer, char c) { .....}}, State2{ public void process(AtsortingbuteLexer atsortingbuteLexer, char c) { .....}} } 

Et ce que vous pouvez faire, c’est faire une carte de la valeur attendue en tant que clé, et enum en tant que valeur,

 Map map map.getOrDefault(key, ParseArrayEnd.State1).process(this, c);