546 votes

Qu'est-ce qu'une énumération et pourquoi est-elle utile ?

Aujourd'hui, je parcourais quelques questions sur ce site et j'ai trouvé une mention d'une enum utilisé dans un modèle unique sur les prétendus avantages d'une telle solution en termes de sécurité des threads.

Je n'ai jamais utilisé enum et moi-même programmons en Java depuis plus de deux ans. Et apparemment, ils ont beaucoup changé. Aujourd'hui, ils soutiennent même pleinement la POO en leur sein.

Pourquoi et pourquoi utiliser les énumérations dans la programmation quotidienne ?

9 votes

1 votes

Vous ne pouvez pas instancier les enums parce qu'ils ont un constructeur privé, ils sont instanciés au démarrage de jvm, donc c'est un singleton, les enums ne sont pas à l'abri des threads.

699voto

sleske Points 29978

Vous devez toujours utiliser les enums lorsqu'une variable (en particulier un paramètre de méthode) ne peut prendre qu'une valeur parmi un petit ensemble de valeurs possibles. Il s'agit par exemple de constantes de type (statut du contrat : "permanent", "temporaire", "apprenti") ou de drapeaux ("exécuter maintenant", "différer l'exécution").

Si vous utilisez des enums au lieu d'entiers (ou de codes de chaînes), vous augmentez la vérification au moment de la compilation et évitez les erreurs dues au passage de constantes non valides, et vous documentez les valeurs qu'il est légal d'utiliser.

BTW, une utilisation excessive des enums peut signifier que vos méthodes en font trop (il est souvent préférable d'avoir plusieurs méthodes séparées, plutôt qu'une méthode qui prend plusieurs drapeaux qui modifient ce qu'elle fait), mais si vous devez utiliser des drapeaux ou des codes de type, les enums sont la voie à suivre.

A titre d'exemple, quelle est la meilleure solution ?

/** Counts number of foobangs.
 * @param type Type of foobangs to count. Can be 1=green foobangs,
 * 2=wrinkled foobangs, 3=sweet foobangs, 0=all types.
 * @return number of foobangs of type
 */
public int countFoobangs(int type)

par rapport à

/** Types of foobangs. */
public enum FB_TYPE {
 GREEN, WRINKLED, SWEET, 
 /** special type for all types combined */
 ALL;
}

/** Counts number of foobangs.
 * @param type Type of foobangs to count
 * @return number of foobangs of type
 */
public int countFoobangs(FB_TYPE type)

Un appel de méthode comme :

int sweetFoobangCount = countFoobangs(3);

devient alors :

int sweetFoobangCount = countFoobangs(FB_TYPE.SWEET);

Dans le deuxième exemple, les types autorisés sont immédiatement clairs, la documentation et l'implémentation ne peuvent pas être désynchronisées, et le compilateur peut faire respecter cette règle. De plus, un appel invalide comme

int sweetFoobangCount = countFoobangs(99);

n'est plus possible.

46 votes

Si vous utilisez des énumérations et souhaitez autoriser une combinaison de valeurs, utilisez EnumSet. Celui-ci est livré avec toutes sortes d'aides utiles, comme public static final EnumSet<FB_TYPE> ALL = EnumSet.allOf(FB_TYPE.class) ;

29 votes

Ces réponses sont celles que j'aime vraiment voir à l'OS parce que quelqu'un y a mis un réel effort, bon travail, je comprends les enums maintenant !

1 votes

Je ne pense pas que ce soit le cas you should *always* use enums when a variable can only take one out of a small set of possible values . L'utilisation de valeurs constantes en tant que "drapeau binaire" (avec un or ) peut s'avérer utile pour les applications en temps réel où l'on manque de mémoire.

148voto

Gene Points 20184

Pourquoi utiliser une fonctionnalité d'un langage de programmation ? Si nous avons des langages, c'est pour

  1. Les programmeurs doivent être en mesure d'assurer l'efficacité et l'efficience de la gestion de l'information. correctement exprimer des algorithmes sous une forme utilisable par les ordinateurs.
  2. Les responsables de la maintenance doivent comprendre les algorithmes que d'autres ont écrits et correctement apporter des modifications.

Les Enums améliorent à la fois la probabilité d'exactitude et la lisibilité sans avoir à écrire de nombreux documents de référence. Si vous êtes prêt à écrire du matériel de bureau, vous pouvez alors "simuler" les enums :

public class Color {
    private Color() {} // Prevent others from making colors.
    public static final Color RED = new Color();
    public static final Color AMBER = new Color();
    public static final Color GREEN = new Color();
}

Vous pouvez maintenant écrire :

Color trafficLightColor = Color.RED;

Le modèle ci-dessus a à peu près le même effet que

public enum Color { RED, AMBER, GREEN };

Les deux fournissent le même niveau d'aide à la vérification de la part du compilateur. Le modèle de base n'est rien d'autre qu'un surcroît de saisie. Mais l'économie d'une grande partie de la saisie rend le programmeur plus efficace (voir 1), il s'agit donc d'une fonctionnalité intéressante.

Cela vaut la peine pour au moins une autre raison :

Déclaration d'échange de données

Une chose que la static final La simulation de l'enum ci-dessus fait pas vous donner est agréable switch cas. Pour les types d'énumération, le commutateur Java utilise le type de sa variable pour déduire la portée des cas d'énumération. enum Color ci-dessus, il suffit de dire :

Color color = ... ;
switch (color) {
    case RED:
        ...
        break;
}

Notez qu'il ne s'agit pas Color.RED dans les affaires. Si vous n'utilisez pas d'enum, la seule façon d'utiliser des quantités nommées avec des switch est quelque chose comme :

public Class Color {
    public static final int RED = 0;
    public static final int AMBER = 1;
    public static final int GREEN = 2;
}

Mais maintenant, une variable qui contient une couleur doit être de type int . L'agréable vérification par le compilateur de l'enum et de l'élément static final La simulation a disparu. Pas de quoi se réjouir.

Un compromis consiste à utiliser un membre à valeur scalaire dans la simulation :

public class Color {
    public static final int RED_TAG = 1;
    public static final int AMBER_TAG = 2;
    public static final int GREEN_TAG = 3;

    public final int tag;

    private Color(int tag) { this.tag = tag; } 
    public static final Color RED = new Color(RED_TAG);
    public static final Color AMBER = new Color(AMBER_TAG);
    public static final Color GREEN = new Color(GREEN_TAG);
}

Aujourd'hui :

Color color = ... ;
switch (color.tag) {
    case Color.RED_TAG:
        ...
        break;
}

Mais attention, encore un peu plus d'éléments de langage !

Utilisation d'un enum en tant que singleton

D'après le modèle ci-dessus, vous pouvez voir pourquoi une énumération permet d'implémenter un singleton. Au lieu d'écrire :

public class SingletonClass {
    public static final void INSTANCE = new SingletonClass();
    private SingletonClass() {}

    // all the methods and instance data for the class here
}

et d'y accéder avec

SingletonClass.INSTANCE

nous pouvons simplement dire

public enum SingletonClass {
    INSTANCE;

    // all the methods and instance data for the class here
}

qui nous donne la même chose. Nous pouvons nous en sortir parce que les enums Java sont mis en œuvre comme des classes à part entière, avec seulement un peu de sucre syntaxique saupoudré sur le dessus. C'est une fois de plus un peu moins de "boilerplate", mais ce n'est pas évident à moins que l'idiome ne vous soit familier. Je n'aime pas non plus le fait que vous obteniez les diverses fonctions enum même si elles n'ont pas beaucoup de sens pour le singleton : ord y values etc. (Il existe en fait une simulation plus délicate dans laquelle Color extends Integer qui fonctionnera avec switch, mais c'est tellement délicat que cela montre encore plus clairement pourquoi enum est une meilleure idée.)

Sécurité des fils

La sécurité des threads n'est un problème potentiel que lorsque des singletons sont créés paresseusement sans verrouillage.

public class SingletonClass {
    private static SingletonClass INSTANCE;
    private SingletonClass() {}
    public SingletonClass getInstance() {
        if (INSTANCE == null) INSTANCE = new SingletonClass();
        return INSTANCE;
    }

    // all the methods and instance data for the class here
}

Si plusieurs threads appellent getInstance simultanément tout en INSTANCE est toujours nul, un nombre quelconque d'instances peut être créé. Ce n'est pas une bonne chose. La seule solution est d'ajouter synchronized l'accès pour protéger la variable INSTANCE .

Toutefois, le static final ci-dessus ne présente pas ce problème. Il crée l'instance avec empressement au moment du chargement de la classe. Le chargement de la classe est synchronisé.

En enum est effectivement paresseux car il n'est pas initialisé avant la première utilisation. L'initialisation Java est également synchronisée, de sorte que plusieurs threads ne peuvent pas initialiser plus d'une instance de INSTANCE . Vous obtenez un singleton initialisé paresseusement avec très peu de code. Le seul point négatif est la syntaxe plutôt obscure. Vous devez connaître l'idiome ou comprendre parfaitement le fonctionnement du chargement et de l'initialisation des classes pour savoir ce qui se passe.

18 votes

Le dernier paragraphe a vraiment clarifié la situation du singleton pour moi. Je vous remercie. Tout lecteur qui ne fait qu'effleurer le sujet devrait relire ce paragraphe.

0 votes

Je lisais ceci, stackoverflow.com/questions/16771373/ . Je ne comprends plus rien à votre dernier paragraphe. En quoi les enum n'offrent-ils pas de fonction d'initialisation des laïcs ?

0 votes

static final sont initialisés à temps d'initialisation de la classe , pas de temps de chargement. C'est exactement la même chose qu'avec enum l'initialisation constante (en fait, ils sont identiques sous le capot). C'est pourquoi il a toujours été inutile d'essayer d'implémenter un code d'initialisation paresseuse "intelligent" pour les singletons, même dans la première version de Java.

47voto

Costi Ciudatu Points 13020

Outre les cas d'utilisation déjà mentionnés, je trouve souvent les enums utiles pour mettre en œuvre le modèle de stratégie, en suivant certaines lignes directrices de base de la POO :

  1. Le code doit se trouver là où se trouvent les données (c'est-à-dire dans l'énumération elle-même, ou souvent dans les constantes de l'énumération, qui peuvent remplacer les méthodes).
  2. Implémentation d'une interface (ou plus) afin de ne pas lier le code client à l'énumération (qui ne devrait fournir qu'un ensemble d'implémentations par défaut).

L'exemple le plus simple est celui d'un ensemble de Comparator et de la mise en œuvre de la politique de l'emploi :

enum StringComparator implements Comparator<String> {
    NATURAL {
        @Override
        public int compare(String s1, String s2) {
            return s1.compareTo(s2);
        }
    },
    REVERSE {
        @Override
        public int compare(String s1, String s2) {
            return NATURAL.compare(s2, s1);
        }
    },
    LENGTH {
        @Override
        public int compare(String s1, String s2) {
            return new Integer(s1.length()).compareTo(s2.length());
        }
    };
}

Ce "modèle" peut être utilisé dans des scénarios beaucoup plus complexes, en faisant un usage intensif de tous les avantages offerts par l'énumération : itération sur les instances, en s'appuyant sur leur ordre implicite, récupération d'une instance par son nom, méthodes statiques fournissant la bonne instance pour des contextes spécifiques, etc. Et tout cela reste caché derrière l'interface, de sorte que votre code fonctionnera avec des implémentations personnalisées sans modification au cas où vous souhaiteriez quelque chose qui n'est pas disponible parmi les "options par défaut".

J'ai vu cela appliqué avec succès pour modéliser le concept de granularité temporelle (quotidienne, hebdomadaire, etc.) où toute la logique était encapsulée dans un enum (choix de la bonne granularité pour une plage de temps donnée, comportement spécifique lié à chaque granularité en tant que méthodes constantes, etc.) ). Et pourtant, le Granularity tel qu'il est perçu par la couche de service est simplement une interface.

4 votes

Vous pouvez également ajouter CASE_INSENSITIVE { @Override public int compare(String s1, String s2) { return s1.compareToIgnoreCase(s2); } . La forme persistante ne contient que le nom de la classe et le nom de la constante, sans dépendre des détails d'implémentation de vos comparateurs.

34voto

orangepips Points 7494

Aucune des autres réponses n'a abordé un aspect qui rend les énumérations particulièrement puissantes, à savoir la possibilité de disposer de méthodes des modèles . Les méthodes peuvent faire partie de l'enum de base et être surchargées par chaque type. Et, avec le comportement attaché à l'enum, il n'est souvent pas nécessaire d'utiliser des constructions if-else ou des instructions de commutation, comme dans le cas suivant L'article de blog démontre - où enum.method() exécute ce qui aurait dû être exécuté à l'intérieur de la conditionnelle. Le même exemple montre également l'utilisation d'importations statiques avec des enums, ce qui permet d'obtenir un code DSL beaucoup plus propre.

Parmi les autres qualités intéressantes, on peut citer le fait que les enums permettent d'implémenter les éléments suivants equals() , toString() y hashCode() et mettre en œuvre Serializable y Comparable .

Pour un aperçu complet de tout ce que les enums ont à offrir, je recommande vivement l'ouvrage de Bruce Eckel intitulé Thinking in Java 4ème édition qui consacre un chapitre entier à ce sujet. Les exemples impliquant un jeu de Roche, Papier, Ciseaux (c'est-à-dire RoShamBo) en tant qu'enums sont particulièrement éclairants.

26voto

CoolBeans Points 11615

De Java documents -

Vous devriez utiliser les types enum à tout moment. devez représenter un ensemble fixe de constantes. Cela inclut les types enum naturels tels que les planètes de notre système solaire solaire et les ensembles de données dont vous connaissez toutes les valeurs possibles au moment de la à la compilation - par exemple, les choix d'un les options d'un menu, les drapeaux de la ligne de commande, etc.

Un exemple courant consiste à remplacer une classe comportant un ensemble de constantes int privées statiques et finales (dans la limite d'un nombre raisonnable de constantes) par un type d'énumération. En fait, si vous pensez connaître toutes les valeurs possibles de "quelque chose" au moment de la compilation, vous pouvez le représenter sous la forme d'un type enum. Les enums offrent plus de lisibilité et de flexibilité qu'une classe avec des constantes.

Peu d'autres avantages me viennent à l'esprit en ce qui concerne les types enum. Il y a toujours une instance d'une classe enum particulière (d'où le concept d'utilisation des enums en tant que singleton). Un autre avantage est que vous pouvez utiliser les enums comme type dans une instruction switch-case. Vous pouvez également utiliser toString() sur l'énumération pour les imprimer sous forme de chaînes lisibles.

8 votes

Utiliseriez-vous une énumération pour les 366 jours de l'année ? Il s'agit d'un ensemble fixe (366) de constantes (les jours ne changent pas). Cela suggère donc que vous devriez :-/ Je limiterais la taille de l'ensemble fixe.

3 votes

@marcog Avec un peu d'ingéniosité, il serait possible de résumer les jours de la semaine et les jours de chaque mois dans une énumération compacte.

2 votes

En parlant de jours de la semaine DayOfWeek est désormais prédéfinie, intégrée dans Java 8 et les versions ultérieures dans le cadre de l'enum java.time (et rétroporté sur Java 6 & 7 y vers Android ).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X