web-dev-qa-db-fra.com

Java: Enum vs Int

Lors de l'utilisation de drapeaux en Java, j'ai observé 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 case-switch.

Je me demandais s'il y avait une différence en termes d'utilisation de la mémoire et de vitesse entre utiliser enums et ints pour les drapeaux?

53
Flynn

ints et enums peuvent tous les deux utiliser le commutateur ou if-then-else, et l'utilisation de la mémoire est également minimale pour les deux, et la vitesse est similaire.

Cependant, la différence la plus importante est la vérification de type. Enums sont cochés, ints ne le sont pas.

Considérons 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 de nombreux 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 présente trois problèmes principaux:

  • Le problème se produit à runtime , not compile time, il sera donc plus coûteux de le réparer et plus difficile d'en trouver la cause.
  • Vous devez écrire du code pour gérer les entrées incorrectes - généralement un if-then-else avec un else throw new IllegalArgumentException("Unknown color " + color); final - encore une fois cher
  • Rien n'empêche une collision de constantes - le code de classe ci-dessus sera compilé même si YELLOW et GREEN ont la même valeur 3

Si vous utilisez enums, vous résolvez tous ces problèmes:

  • Votre code ne sera compilé que si vous transmettez des valeurs valides dans
  • Pas besoin de code "mauvaise entrée" spécial - le compilateur le gère pour vous
  • Les valeurs Enum sont uniques
120
Bohemian

Vous pouvez même utiliser Enums pour remplacer les drapeaux combinés au niveau des bits tels que int flags = FLAG_1 | FLAG_2;

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

Set<FlagEnum> 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 bits et que l'implémentation doit être suffisamment performante pour remplacer les indicateurs int-based.

10
the-banana-king

L'utilisation de la mémoire et la vitesse ne sont pas les considérations qui importent. De toute façon, vous ne pourriez pas mesurer une différence.

Je pense que les enums devraient être préférés quand ils s'appliquent, car ils soulignent le fait que les valeurs choisies vont de pair et forment un ensemble fermé. La lisibilité est également grandement améliorée. Le code utilisant des enums est plus auto-documenté que les valeurs de stand int dispersées dans votre code.

Préférer les enums.

9
duffymo

L'une des raisons pour lesquelles vous verrez du code utilisant des indicateurs int au lieu d'un enum est que Java n'avait pas d'énumération avant Java 1.5.

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

L'utilisation d'un indicateur int est encore préférable dans le code Java moderne dans un très petit nombre de cas, mais dans la plupart des cas, préférez-vous utiliser un enum, en raison de la sécurité et de l'expressivité qu'ils offrent.

En termes d'efficacité, cela dépendra de la manière dont ils sont utilisés. La machine virtuelle Java gère les deux types de manière très efficace, mais la méthode int serait probablement légèrement plus efficace dans certains cas d'utilisation (car ils sont gérés comme des primitives plutôt que comme des objets), mais dans d'autres cas, l'énumération serait plus efficace (car elle ne fonctionne pas correctement. pas besoin d’aller jeter la boxe/unboxing). 

Vous auriez du mal à trouver une situation dans laquelle la différence d'efficacité serait perceptible de quelque manière que ce soit dans une application réelle , vous devez donc prendre une décision en fonction de la qualité du code (lisibilité et sécurité), qui devrait vous conduire à utiliser un enum 99% du temps .

6
Tim

Oui, il y a une différence. En Java 64 bits moderne, les valeurs Enum sont essentiellement des pointeurs sur des objets 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 enums (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(String[] 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 (c.e == Enum.ONE) {
                i1++;
            } else if (c.e == 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 (c.i == 1) {
                i1++;
            } else if (c.i == 2) {
                i2++;
            } else {
                i3++;
            }
        }

        return i1 + i2*2 + i3*3;
    }
}
3
Sergey Alaev

Gardez à l'esprit que enums respecte le type et que vous ne pouvez pas mélanger les valeurs d'une énumération à une autre. C'est une bonne raison de préférer enums à ints pour les drapeaux.

D'autre part, si vous utilisez ints pour vos constantes, vous pouvez mélanger des valeurs provenant de constantes non liées, 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 de enums sur ints est négligeable, et le type de sécurité enums fourni rend la surcharge minimale acceptable.

3
Óscar López

Réponse à votre question: Non, après un temps négligeable de chargement de la classe Enum, les performances sont identiques.

Comme d'autres l'ont indiqué, les deux types peuvent être utilisés dans des instructions switch ou if else. En outre, comme d’autres l’ont déjà dit, vous devriez privilégier Enums plutôt que les indicateurs int, 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. Indiquez la valeur que votre instruction switch/if est supposé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 motif fourni pour donner aux planètes des masses et des rayons. Fournir la propriété de cette manière garantit que vous n'oublierez pas de couvrir une affaire si vous ajoutez une énumération. 

2
Joe

J'aime utiliser Enums lorsque cela est possible, mais j’ai été confronté à une situation dans laquelle je devais calculer des millions de décalages de fichiers pour différents types de fichiers que j’avais définis dans une énumération et où j’ai dû exécuter une instruction switch des dizaines de millions de fois pour calculer la base de décalage sur le type enum. J'ai couru le test suivant:

import Java.util.Random;

classe publique switchTest { Énumération publique MyEnum { Valeur1, Valeur2, Valeur3, Valeur4, Valeur5 };

public static void main(String[] args)
{
    final String s1 = "Value1";
    final String s2 = "Value2";
    final String s3 = "Value3";
    final String s4 = "Value4";
    final String s5 = "Value5";

    String[] strings = new String[]
    {
        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

enums: 455

ints: 362

J'ai donc décidé que pour moi, les enums étaient suffisamment efficaces. Lorsque j'ai diminué le nombre de boucles de 10M à 1M, la chaîne et les enums ont pris environ deux fois plus de temps que l'int, ce qui indique qu'il y a eu un surcoût lié à l'utilisation de chaînes et d'énums pour la première fois par rapport à ints.

2
user3708686

Même si cette question est ancienne, je voudrais souligner ce que vous ne pouvez pas faire avec ints

public interface AttributeProcessor {
    public void process(AttributeLexer attributeLexer, char c);
}

public enum ParseArrayEnd implements AttributeProcessor {
    State1{
        public void process(AttributeLexer attributeLexer, char c) {
            .....}},
    State2{
        public void process(AttributeLexer attributeLexer, char c) {
            .....}}
}

Et ce que vous pouvez faire est d’établir une carte de la valeur attendue en tant que clé et de l’énum en tant que valeur, 

Map<String, AttributeProcessor> map 
map.getOrDefault(key, ParseArrayEnd.State1).process(this, c);
0
John