Quel est l'avantage d'une énumération Java par rapport à une classe avec des champs finaux statiques publics?

147

Je connais très bien le C # mais je commence à travailler davantage en Java. Je m'attendais à apprendre que les énumérations en Java étaient fondamentalement équivalentes à celles en C # mais apparemment ce n'est pas le cas. Au départ, j'étais ravi d'apprendre que les énumérations Java pouvaient contenir plusieurs éléments de données, ce qui semble très avantageux ( http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html ). Cependant, depuis lors, j'ai trouvé de nombreuses fonctionnalités manquantes qui sont triviales en C #, telles que la possibilité d'attribuer facilement à un élément enum une certaine valeur, et par conséquent la possibilité de convertir un entier en enum sans un effort décent ( c'est-à-dire Convertir une valeur entière en Java Enum correspondant ).

Ma question est donc la suivante: y a-t-il un avantage aux énumérations Java par rapport à une classe avec un tas de champs finaux statiques publics? Ou fournit-il simplement une syntaxe plus compacte?

EDIT: Permettez-moi d'être plus clair. Quel est l'avantage des énumérations Java sur une classe avec un tas de champs finaux statiques publics du même type ? Par exemple, dans l'exemple des planètes au premier lien, quel est l'avantage d'une énumération sur une classe avec ces constantes publiques:

public static final Planet MERCURY = new Planet(3.303e+23, 2.4397e6);
public static final Planet VENUS = new Planet(4.869e+24, 6.0518e6);
public static final Planet EARTH = new Planet(5.976e+24, 6.37814e6);
public static final Planet MARS = new Planet(6.421e+23, 3.3972e6);
public static final Planet JUPITER = new Planet(1.9e+27, 7.1492e7);
public static final Planet SATURN = new Planet(5.688e+26, 6.0268e7);
public static final Planet URANUS = new Planet(8.686e+25, 2.5559e7);
public static final Planet NEPTUNE = new Planet(1.024e+26, 2.4746e7);

Pour autant que je sache, la réponse de casablanca est la seule qui satisfait à cela.

Craig W
la source
4
@Bohemian: Ce n'est peut-être pas un doublon, puisque l'OP ne mentionnait que des public static finalchamps, qui peuvent être des valeurs typées et pas nécessairement des ints.
casablanca
1
@Shahzeb à peine. Utiliser clairement des énumérations au lieu de constantes de chaîne est une EXTRÊMEMENT bonne idée et plus qu'encouragée. Type-safety, n'a pas besoin de fonctions utilitaires statiques, etc. absolument aucune raison d'utiliser des chaînes à la place.
Voo le
1
@Voo Oui, je savais qu'il y aurait des désaccords. Et en voici un dans 49 secondes. Les énumérations sont excellentes (et je les aime et les utilise très souvent) mais de quel type de sécurité avez-vous besoin lorsque vous déclarez une constante ou que vous l'utilisez. Il est exagéré de créer une énumération chaque fois que vous devez déclarer une constante pour String littéral.
Shahzeb
4
@Shahzeb Si vous avez une seule variable, utilisez bien sûr une chaîne, il ne peut pas se passer grand chose ici (une seule valeur est plutôt inutile en tant que paramètre). Mais notez que nous parlons de constante S, donc maintenant nous parlons de les transmettre probablement à des fonctions, etc. Avons-nous besoin de la sécurité de type? Eh bien, non, mais la plupart des gens considèrent les types de style C de "tout est un void*" bon style et cela peut arrêter les bogues (surtout si vous passez plus d'un paramètre enum / string!). De plus, il place les constantes dans leur propre espace de noms, etc. Contrairement à cela, il n'y a pas de réel avantage à avoir des variables simples.
Voo le
3
@Bohemian: Je ne vois pas comment. Avec ints, il n'y a pas de sécurité de type car on pourrait passer n'importe quelle valeur. Les objets typés, en revanche, ne sont pas différents des énumérations en termes de sécurité de type.
casablanca

Réponses:

78

Techniquement, on pourrait en effet voir les énumérations comme une classe avec un tas de constantes typées, et c'est en fait ainsi que les constantes enum sont implémentées en interne. enumCependant, l' utilisation d'un vous donne des méthodes utiles ( Enum javadoc ) que vous auriez autrement à implémenter vous-même, telles que Enum.valueOf.

casablanca
la source
14
Il y a aussi .values()à parcourir la liste des valeurs.
h3xStream
1
Cela semble être la bonne réponse, même si ce n'est pas très satisfaisant. À mon avis, il ne valait guère la peine pour Java d'ajouter le support des enums uniquement pour la syntaxe plus compacte et l'accès aux méthodes Enum.
Craig W
7
@Craig votre instinct a raison - c'est une très mauvaise réponse, car elle a complètement manqué le but des énumérations. Voir mes commentaires sous la question pour une partie des raisons.
Bohème
1
@Bohemian: Je n'ai pas "raté le but" des énumérations - je les utilise tout le temps. Voir ma réponse à votre commentaire ci-dessus.
casablanca
1
Enum.valuesOf renvoie le même objet si elle est appelée deux fois?
Emre Aktürk
104
  1. Sécurité de type et sécurité de valeur.
  2. Singleton garanti.
  3. Capacité à définir et à remplacer des méthodes.
  4. Capacité d'utiliser des valeurs dans switchinstruction des casedéclarations sans réserve.
  5. Séquentialisation intégrée des valeurs via ordinal().
  6. Sérialisation par nom et non par valeur, ce qui offre un certain degré de pérennité.
  7. EnumSetet les EnumMapclasses.
Marquis de Lorne
la source
19
Cela dit, chaque fois que j'ai mis du code dans un Enum, je l'ai regretté.
Marquis of Lorne
4
Pourquoi l'avez-vous regretté? Je n'ai jamais…
glglgl
2
@glglgl Becasue, il a mis le code spécifique à l'application dans un endroit où je sentais qu'il n'appartenait pas vraiment, qui ne définissait en réalité qu'un ensemble de valeurs. Si je l'avais fait à nouveau, je l'aurais inclus dans l'une des nombreuses switchdéclarations qui ont motivé à l'origine l'utilisation d'un Enum.
Marquis of Lorne
72

Personne n'a mentionné la possibilité de les utiliser dans les switchdéclarations; Je vais ajouter cela aussi.

Cela permet d'utiliser des énumérations arbitrairement complexes d'une manière propre sans utiliser de séquences instanceofpotentiellement déroutantes ifou de valeurs de commutation non-string / int. L'exemple canonique est une machine à états.

Dave Newton
la source
Quoi qu'il en soit, vous n'avez mentionné aucun avantage des enums par rapport aux champs statiques, vous pouvez utiliser suffisamment de types dans les instructions de commutation avec des champs statiques. Op Besoin de vrais fonctionnels ou de différences de performances
Genaut
@Genaut L'avantage est que les énumérations ont plus de fonctionnalités qu'une chaîne ou un int - la question portait sur les différences, ce que j'ai fourni. L'OP est déjà au courant de ce que sont les énumérations, et personne d'autre n'avait mentionné les instructions de commutation lorsque j'ai publié ceci il y a 4,5 ans, et au moins quelques personnes l'ont trouvé fourni de nouvelles informations ¯_ (ツ) _ / ¯
Dave Newton
44

Le principal avantage est la sécurité du type. Avec un ensemble de constantes, toute valeur du même type intrinsèque pourrait être utilisée, introduisant des erreurs. Avec une énumération, seules les valeurs applicables peuvent être utilisées.

Par exemple

public static final int SIZE_SMALL  = 1;
public static final int SIZE_MEDIUM = 2;
public static final int SIZE_LARGE  = 3;

public void setSize(int newSize) { ... }

obj.setSize(15); // Compiles but likely to fail later

contre

public enum Size { SMALL, MEDIUM, LARGE };

public void setSize(Size s) { ... }

obj.setSize( ? ); // Can't even express the above example with an enum
Jim Garrison
la source
3
les classes sont également de type sécurisé ...: / (en supposant que les champs statiques sont du type de la classe conteneur)
h3xStream
Vous pouvez toujours lui transmettre une valeur non valide, mais ce serait une erreur de compilation qui est beaucoup plus facile à repérer.
zzzzzzzzzzzzzzzzzzzzzzzzzzzz
2
Vous pouvez appeler setSize(null)votre deuxième exemple, mais il est susceptible d'échouer beaucoup plus tôt que l'erreur du premier exemple.
Jeffrey
42

Il y a moins de confusion. Prenons Fontpar exemple. Il a un constructeur qui prend le nom de celui que Fontvous voulez, sa taille et son style ( new Font(String, int, int)). À ce jour, je ne me souviens pas si le style ou la taille passe en premier. Si Fontavait utilisé un enumpour tous ses différents styles ( PLAIN, BOLD, ITALIC, BOLD_ITALIC), son constructeur ressemblerait Font(String, Style, int), ce qui empêche toute confusion. Malheureusement, il enumn'y en avait pas lorsque la Fontclasse a été créée, et puisque Java doit maintenir la compatibilité inverse, nous serons toujours en proie à cette ambiguïté.

Bien sûr, ce n'est qu'un argument pour utiliser un enumau lieu de public static finalconstantes. Les énumérations sont également parfaites pour les singletons et implémentent un comportement par défaut tout en permettant une personnalisation ultérieure (IE le modèle de stratégie ). Un exemple de ce dernier est java.nio.file's OpenOptionet StandardOpenOption: si un développeur voulait créer son propre non-standard OpenOption, il le pourrait.

Jeffrey
la source
Votre cas 'Font' est toujours ambigu si deux des mêmes énumérations ont été demandées. La réponse canonique à ce problème est ce que de nombreux langages appellent les paramètres nommés . Cela ou mieux support de signature de méthode IDE.
aaaaaa
1
@aaaaaa Je n'ai pas vu beaucoup de cas où un constructeur en prendrait deux enumsans utiliser varargs ou a Setpour en prendre un nombre arbitraire.
Jeffrey
@aaaaaa Le principal problème avec les paramètres nommés dépend des détails d'implémentation (le nom du paramètre). Je peux créer une interface interface Foo { public void bar(String baz); }. Quelqu'un fait une classe qui invoque bar: someFoo.bar(baz = "hello");. Je change la signature de Foo::baren public void bar(String foobar). Désormais, la personne qui a appelé someFoodevra modifier son code si elle souhaite toujours qu'il fonctionne.
Jeffrey
Je ne me souviens pas non plus d'avoir vu des types d'énumérations consécutifs, mais je pensais qu'un commun pourrait être DAY_OF_WEEK ou quelque chose de similaire. Et bon point à propos de l'interface - n'y avait pas pensé. Personnellement, je prendrais ce problème au-dessus de l'ambiguïté généralisée causée par des paramètres non nommés, nécessitant un support IDE plus fort. Cependant, je comprends que c'est une question de jugement, et que les changements de rupture d'API sont quelque chose à considérer sérieusement.
aaaaaa
26

Il y a beaucoup de bonnes réponses ici, mais aucune ne mentionne qu'il existe des implémentations hautement optimisées des classes / interfaces de l'API Collection spécifiquement pour les énumérations :

Ces classes spécifiques à une énumération n'acceptent que les Enuminstances (les EnumMapseules acceptent les Enums uniquement comme clés), et chaque fois que possible, elles reviennent à une représentation compacte et à la manipulation de bits dans leur implémentation.

Qu'est-ce que ça veut dire?

Si notre Enumtype n'a pas plus de 64 éléments (la plupart des Enumexemples réels seront qualifiés pour cela), les implémentations stockent les éléments dans une seule longvaleur, chaque Enuminstance en question sera associée à un peu de cette longueur de 64 bits long. Ajouter un élément à un EnumSetest simplement définir le bit approprié à 1, le supprimer revient simplement à mettre ce bit à 0. Tester si un élément est dans le Setest juste un test de masque de bits! Maintenant tu dois aimer Enumça!

icza
la source
1
J'étais au courant de ces deux avant, mais j'ai juste appris beaucoup de votre What does this mean?section. Je savais qu'il y avait un fossé dans la mise en œuvre à la taille 64, mais je ne savais pas vraiment pourquoi
Christopher Rucinski
15

exemple:

public class CurrencyDenom {
   public static final int PENNY = 1;
 public static final int NICKLE = 5;
 public static final int DIME = 10;
public static final int QUARTER = 25;}

Limitation des constantes Java

1) Pas de sécurité de type : tout d'abord, ce n'est pas de type sûr; vous pouvez attribuer n'importe quelle valeur int valide à int, par exemple 99 bien qu'il n'y ait pas de pièce pour représenter cette valeur.

2) Aucune impression significative : l'impression de la valeur de l'une de ces constantes imprimera sa valeur numérique au lieu du nom significatif de la pièce, par exemple lorsque vous imprimez NICKLE, il imprimera "5" au lieu de "NICKLE"

3) Pas d'espace de noms : pour accéder à la constante currencyDenom, nous devons préfixer le nom de la classe, par exemple CurrencyDenom.PENNY au lieu d'utiliser simplement PENNY bien que cela puisse également être réalisé en utilisant l'importation statique dans JDK 1.5

Avantage d'énumération

1) Les énumérations en Java sont de type sécurisé et ont leur propre espace de nom. Cela signifie que votre énumération aura un type par exemple "Devise" dans l'exemple ci-dessous et que vous ne pouvez attribuer aucune valeur autre que celle spécifiée dans Constantes Enum.

public enum Currency {PENNY, NICKLE, DIME, QUARTER};

Currency coin = Currency.PENNY; coin = 1; //compilation error

2) Enum en Java est un type de référence comme la classe ou l'interface et vous pouvez définir un constructeur, des méthodes et des variables dans java Enum, ce qui le rend plus puissant que Enum en C et C ++ comme indiqué dans l'exemple suivant de type Java Enum.

3) Vous pouvez spécifier les valeurs des constantes d'énumération au moment de la création comme indiqué dans l'exemple ci-dessous: public enum Currency {PENNY (1), NICKLE (5), DIME (10), QUARTER (25)}; Mais pour que cela fonctionne, vous devez définir une variable membre et un constructeur car PENNY (1) appelle en fait un constructeur qui accepte la valeur int, voir l'exemple ci-dessous.

public enum Currency {
    PENNY(1), NICKLE(5), DIME(10), QUARTER(25);
    private int value;

    private Currency(int value) {
            this.value = value;
    }
}; 

Référence: https://javarevisited.blogspot.com/2011/08/enum-in-java-example-tutorial.html

Maverick
la source
11

Le premier avantage des énumérations, comme vous l'avez déjà remarqué, est la simplicité de la syntaxe. Mais le point principal des énumérations est de fournir un ensemble bien connu de constantes qui, par défaut, forment une plage et aident à effectuer une analyse de code plus complète grâce à des contrôles de sécurité de type et de valeur.

Ces attributs d'énumérations aident à la fois un programmeur et un compilateur. Par exemple, disons que vous voyez une fonction qui accepte un entier. Que pourrait signifier cet entier? Quel genre de valeurs pouvez-vous transmettre? Vous ne savez pas vraiment tout de suite. Mais si vous voyez une fonction qui accepte enum, vous connaissez très bien toutes les valeurs possibles que vous pouvez transmettre.

Pour le compilateur, les énumérations aident à déterminer une plage de valeurs et à moins que vous n'assigniez des valeurs spéciales aux membres d'énumération, elles sont bien comprises entre 0 et plus. Cela permet de détecter automatiquement les erreurs dans le code grâce à des contrôles de sécurité de type et plus encore. Par exemple, le compilateur peut vous avertir que vous ne gérez pas toutes les valeurs d'énumération possibles dans votre instruction switch (c'est-à-dire lorsque vous n'avez pas de defaultcasse et ne gérez qu'une seule valeur d'énumération sur N). Il vous avertit également lorsque vous convertissez un entier arbitraire en enum car la plage de valeurs d'énumération est inférieure à celle d'un entier et cela peut à son tour déclencher des erreurs dans la fonction qui n'accepte pas vraiment un entier. En outre, la génération d'une table de saut pour le commutateur devient plus facile lorsque les valeurs sont comprises entre 0 et plus.

Ce n'est pas seulement vrai pour Java, mais aussi pour d'autres langages avec une vérification de type stricte. C, C ++, D, C # sont de bons exemples.


la source
4

Un ENUM est implictly finale, avec les constructeurs privés, toutes ses valeurs sont du même type ou un sous-type, vous pouvez obtenir toutes ses valeurs à l' aide values(), obtient sa name()ou ordinal()valeur ou vous pouvez rechercher un ENUM par numéro ou le nom.

Vous pouvez également définir des sous-classes (même si elles sont théoriquement finales, ce que vous ne pouvez pas faire autrement)

enum Runner implements Runnable {
    HI {
       public void run() {
           System.out.println("Hello");
       }
    }, BYE {
       public void run() {
           System.out.println("Sayonara");
       }
       public String toString() {
           return "good-bye";
       }
    }
 }

 class MYRunner extends Runner // won't compile.
Peter Lawrey
la source
4

enum Avantages:

  1. Les énumérations sont de type sécurisé, les champs statiques ne le sont pas
  2. Il existe un nombre fini de valeurs (il n'est pas possible de transmettre une valeur d'énumération non existante. Si vous avez des champs de classe statiques, vous pouvez faire cette erreur)
  3. Chaque énumération peut avoir plusieurs propriétés (champs / getters) assignées - encapsulation. Aussi quelques méthodes simples: YEAR.toSeconds () ou similaire. Comparez: Colors.RED.getHex () avec Colors.toHex (Colors.RED)

"comme la possibilité d'attribuer facilement à un élément enum une certaine valeur"

enum EnumX{
  VAL_1(1),
  VAL_200(200);
  public final int certainValue;
  private X(int certainValue){this.certainValue = certainValue;}
}

"et par conséquent la possibilité de convertir un entier en enum sans un effort décent" Ajouter une méthode convertissant int en enum qui fait cela. Ajoutez simplement HashMap statique <Integer, EnumX> contenant l' énumération java de mappage .

Si vous voulez vraiment convertir ord = VAL_200.ordinal () en val_200, utilisez simplement: EnumX.values ​​() [ord]

mabn
la source
3

Une autre différence importante est que le compilateur java traite les static finalchamps de types primitifs et String comme des littéraux. Cela signifie que ces constantes deviennent en ligne. C'est similaire au C/C++ #definepréprocesseur. Voir cette question SO . Ce n'est pas le cas avec les énumérations.

Seyed Jalal Hosseini
la source
2

Vous obtenez une vérification à la compilation des valeurs valides lorsque vous utilisez une énumération. Regardez cette question.

zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
la source
2

Le plus grand avantage est que les singletons enum sont faciles à écrire et sécurisés pour les threads:

public enum EasySingleton{
    INSTANCE;
}

et

/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
     private volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

les deux sont similaires et ont géré la sérialisation par eux-mêmes en implémentant

//readResolve to prevent another instance of Singleton
    private Object readResolve(){
        return INSTANCE;
    }

plus

Premraj
la source
0

Je pense qu'un enumne peut pas être final, car sous le capot, le compilateur génère des sous-classes pour chaqueenum entrée.

Plus d'informations De la source

Sitansu
la source
En interne, ils ne sont pas définitifs, car - comme vous le dites - peuvent être sous-classés en interne. Mais hélas, vous ne pouvez pas les sous-classer vous-même, par exemple pour les étendre avec vos propres valeurs.
glglgl
0

Les énumérations affichées ici présentent de nombreux avantages et je crée actuellement de telles énumérations, comme demandé dans la question. Mais j'ai une énumération avec 5-6 champs.

enum Planet{
EARTH(1000000, 312312321,31232131, "some text", "", 12),
....
other planets
....

Dans ce genre de cas, lorsque vous avez plusieurs champs dans les énumérations, il est beaucoup plus difficile de comprendre quelle valeur appartient à quel champ car vous avez besoin de voir le constructeur et le globe oculaire.

La classe avec des static finalconstantes et l'utilisation d'un Buildermodèle pour créer de tels objets le rendent plus lisible. Mais, vous perdriez tous les autres avantages de l'utilisation d'une énumération, si vous en avez besoin. Un inconvénient de ces classes est, vous devez ajouter les Planetobjets manuellement à list/setdesPlanets.

Je préfère toujours enum à une telle classe, car c'est values()pratique et vous ne savez jamais si vous en avez besoin pour l'utiliser dans switchou EnumSetou EnumMapà l'avenir :)

Bikas Katwal
la source
0

Raison principale: les énumérations vous aident à écrire du code bien structuré où la signification sémantique des paramètres est claire et fortement typée au moment de la compilation - pour toutes les raisons que d'autres réponses ont données.

Quid pro quo: en Java prêt à l'emploi, le tableau des membres d'un Enum est définitif. C'est normalement bien car cela aide à valoriser la sécurité et les tests, mais dans certaines situations, cela pourrait être un inconvénient, par exemple si vous étendez le code de base existant peut-être à partir d'une bibliothèque. En revanche, si les mêmes données sont dans une classe avec des champs statiques, vous pouvez facilement ajouter de nouvelles instances de cette classe au moment de l'exécution (vous devrez peut-être également écrire du code pour les ajouter à tout Iterable que vous avez pour cette classe). Mais ce comportement d'Enums peut être changé: en utilisant la réflexion, vous pouvez ajouter de nouveaux membres à l'exécution ou remplacer des membres existants, bien que cela ne devrait probablement être fait que dans des situations spécialisées où il n'y a pas d'alternative: c'est-à-dire que c'est une solution piratée et peut produire des problèmes inattendus, voir ma réponse surPuis-je ajouter et supprimer des éléments d'énumération lors de l'exécution en Java .

radfast
la source