Comparaison des membres de l'énumération Java: == ou equals ()?

1736

Je sais que les énumérations Java sont compilées en classes avec des constructeurs privés et un groupe de membres statiques publics. Lorsque je compare deux membres d'une énumération donnée, j'ai toujours utilisé .equals(), par exemple

public useEnums(SomeEnum a)
{
    if(a.equals(SomeEnum.SOME_ENUM_VALUE))
    {
        ...
    }
    ...
}

Cependant, je suis juste tombé sur un code qui utilise l'opérateur égal ==au lieu de .equals ():

public useEnums2(SomeEnum a)
{
    if(a == SomeEnum.SOME_ENUM_VALUE)
    {
        ...
    }
    ...
}

Quel opérateur dois-je utiliser?

Matt Ball
la source
7
Je suis juste tombé sur une question très similaire: stackoverflow.com/questions/533922/…
Matt Ball
39
Je suis surpris que dans toutes les réponses (en particulier celle des polygénelubrifiants qui explique en détail pourquoi == fonctionne) qu'un autre gros avantage de == n'a pas été mentionné: qu'il rend explicite le fonctionnement des énumérations (comme un ensemble fixe de singleton objets). Avec des égaux, cela conduit à penser qu'il peut y avoir en quelque sorte plusieurs instances de la même «alternative» enum flottant autour.
Stuart Rossiter
6
Rester simple. "==" est plus lisible et fonctionne car les énumérations sont singleton par défaut. Ref
lokesh

Réponses:

1578

Les deux sont techniquement corrects. Si vous regardez le code source .equals(), il s'en remet simplement à ==.

J'utilise ==, cependant, car ce sera sans danger.

Révérend Gonzo
la source
64
Personnellement, je n'aime pas la «sécurité nulle» mentionnée comme raison d'utiliser ==.
Nivas
258
@Nivas: pourquoi pas? Aimez-vous vous soucier de l'ordre de vos arguments (qui ne s'applique qu'à la comparaison des constantes de gauche, comme dans la réponse de Pascal)? Aimez-vous toujours vérifier qu'une valeur n'est pas nulle avant de l' .equals()intégrer? Je sais que non.
Matt Ball
86
Gardez à l'esprit que lors de la lecture de votre code, le "==" peut apparaître incorrect au lecteur jusqu'à ce qu'il s'éteigne et regarde la source du type impliqué, puis voit qu'il s'agit d'une énumération. En ce sens, il est moins gênant de lire ".equals ()".
Kevin Bourrillion
60
@Kevin - si vous n'utilisez pas un IDE qui vous permet de voir les types directement lors de la visualisation de la source, vous vous trompez - pas un problème avec un IDE approprié.
MetroidFan2002
63
Parfois, le code est refactorisé et il est possible qu'une énumération soit remplacée par une classe, auquel cas == n'est plus garanti d'être correct, tandis qu'equals continue de fonctionner de manière transparente. Pour cette raison, égal est clairement un meilleur choix. Si vous voulez une sécurité nulle (parce que vous travaillez sur une base de code qui n'a pas été écrite de manière à minimiser l'utilisation des valeurs nulles), il y a égal Apache ObjectUtils ou Guava's Objects # (ou vous pouvez rouler le vôtre). Je ne mentionnerai même pas le mot "performance" de peur que quelqu'un me gifle à juste titre avec un livre de Donald Knuth.
laszlok
1113

Peut ==être utilisé sur enum?

Oui: les énumérations ont des contrôles d'instance serrés qui vous permettent d'utiliser ==pour comparer des instances. Voici la garantie fournie par la spécification de la langue (je souligne):

JLS 8.9 Enums

Un type enum n'a pas d'instances autres que celles définies par ses constantes enum.

Il s'agit d'une erreur au moment de la compilation de tenter d'instancier explicitement un type d'énumération. La final cloneméthode in Enumgarantit que les enumconstantes ne peuvent jamais être clonées, et le traitement spécial par le mécanisme de sérialisation garantit que les instances en double ne sont jamais créées à la suite de la désérialisation. L'instanciation réfléchie des types d'énumération est interdite. Ensemble, ces quatre éléments garantissent qu'aucune instance d'un enumtype n'existe au-delà de celles définies par les enumconstantes.

Puisqu'il n'y a qu'une seule instance de chaque enumconstante, il est permis d'utiliser l' ==opérateur à la place de la equalsméthode lors de la comparaison de deux références d'objet s'il est connu qu'au moins l'une d'entre elles se réfère à une enumconstante . (La equalsméthode in Enumest une finalméthode qui invoque simplement super.equalsson argument et renvoie le résultat, effectuant ainsi une comparaison d'identité.)

Cette garantie est suffisamment forte pour que Josh Bloch recommande que si vous insistez pour utiliser le modèle singleton, la meilleure façon de l'implémenter est d'utiliser un seul élément enum(voir: Effective Java 2nd Edition, Item 3: Enforce the singleton property with a constructeur privé ou type enum ; également Thread safety in Singleton )


Quelles sont les différences entre ==et equals?

Pour rappel, il faut dire qu'en général, ce ==n'est PAS une alternative viable à equals. Quand c'est le cas (comme avec enum), il y a deux différences importantes à considérer:

== ne jette jamais NullPointerException

enum Color { BLACK, WHITE };

Color nothing = null;
if (nothing == Color.BLACK);      // runs fine
if (nothing.equals(Color.BLACK)); // throws NullPointerException

== est soumis à une vérification de compatibilité des types lors de la compilation

enum Color { BLACK, WHITE };
enum Chiral { LEFT, RIGHT };

if (Color.BLACK.equals(Chiral.LEFT)); // compiles fine
if (Color.BLACK == Chiral.LEFT);      // DOESN'T COMPILE!!! Incompatible types!

Doit ==être utilisé le cas échéant?

Bloch mentionne spécifiquement que les classes immuables qui ont un contrôle approprié sur leurs instances peuvent garantir à leurs clients qu'elles ==sont utilisables. enumest spécifiquement mentionné pour illustrer.

Point 1: envisager des méthodes d'usine statiques plutôt que des constructeurs

[...] il permet à une classe immuable de garantir qu'il n'existe pas deux instances égales: a.equals(b)si et seulement si a==b. Si une classe fait cette garantie, ses clients peuvent utiliser l' ==opérateur à la place de la equals(Object)méthode, ce qui peut améliorer les performances. Les types d'énumération offrent cette garantie.

Pour résumer, les arguments pour utiliser ==on enumsont:

  • Ça marche.
  • C'est plus rapide.
  • C'est plus sûr à l'exécution.
  • C'est plus sûr au moment de la compilation.
polygénelubrifiants
la source
27
Belle réponse mais ... 1. Ça marche : d'accord 2. c'est plus rapide : le prouver pour les énumérations :) 3. C'est plus sûr au moment de l'exécution : Color nothing = null;est IMHO un bug et devrait être corrigé, votre énumération a deux valeurs, pas trois ( voir le commentaire de Tom Hawtin) 4. Il est plus sûr au moment de la compilation : OK, mais equalsreviendrait quand même false. Au final, je ne l'achète pas, je préfère equals()pour la lisibilité (voir le commentaire de Kevin).
Pascal Thivent
201
C'est une marque noire contre Java que certaines personnes pensent que foo.equals(bar)c'est plus lisible quefoo == bar
Bennett McElwee
19
Cela fonctionne: jusqu'à ce que vous ayez besoin de remplacer une énumération par une classe dans le cadre d'une refactorisation. Bonne chance pour trouver tout le code en utilisant == qui casse. C'est plus rapide: même si c'est vrai (discutable), il y a cette citation de Knuth sur l'optimisation prématurée. Plus sûr au moment de l'exécution: "Safe" n'est pas le premier mot qui vous vient à l'esprit si l'utilisation de == est la seule chose qui vous sépare d'une NullPointerException. Plus sûr à la compilation: pas vraiment. Cela peut vous empêcher de l'erreur plutôt élémentaire de comparer les mauvais types, mais encore une fois, si c'est comme ça que vos programmeurs sont stupides, "sûr" est la seule chose que vous n'êtes pas.
laszlok
35
"Si c'est comme ça que vos programmeurs sont stupides," sûr "est la seule chose que vous n'êtes pas." - Je ne suis pas d'accord avec cette citation. C'est à cela que sert la vérification de type: éviter les erreurs "stupides" au moment de la compilation, donc même avant l'exécution du code. Même les gens intelligents font des erreurs stupides, meilleures garanties que promesses.
ymajoros
7
@laszlok: bien sûr, mais si les choses peuvent échouer, elles le feront. Avoir une clôture pour certaines erreurs stupides est toujours une bonne chose. Il y a beaucoup de place pour être créatif pour des bogues moins triviaux, laissez le compilateur gérer celui-ci avant d'essayer d'exécuter votre code.
ymajoros
88

Utiliser ==pour comparer deux valeurs d'énumération fonctionne, car il n'y a qu'un seul objet pour chaque constante d'énumération.

Sur une note latérale, il n'est en fait pas nécessaire d'utiliser ==pour écrire du code null-safe, si vous écrivez votre equals()comme ceci:

public useEnums(final SomeEnum a) {
    if (SomeEnum.SOME_ENUM_VALUE.equals(a)) {
        
    }
    
}

Il s'agit d'une meilleure pratique connue sous le nom de comparaison des constantes de gauche que vous devez absolument suivre.

Pascal Thivent
la source
9
Je le fais lors de l' .equals()ingénierie des valeurs de chaîne, mais je pense toujours que c'est une façon merdique d'écrire du code null-safe. Je préférerais de loin avoir un opérateur (ou une méthode, mais je sais que [objet nul] .equals ne sera jamais sans valeur nulle en Java en raison du fonctionnement de l'opérateur point) qui est toujours sans valeur nulle, quel que soit le l'ordre dans lequel il est utilisé. Sémantiquement, l'opérateur «égal» doit toujours commuter.
Matt Ball
2
Eh bien, comme Java n'a pas l'opérateur de navigation sécurisée de Groovy ( ?.), je continuerai à utiliser cette pratique lors de la comparaison avec les constantes et, TBH, je ne le trouve pas merdique, peut-être juste moins "naturel".
Pascal Thivent
32
Une nullénumération est généralement une erreur. Vous avez cette énumération de toutes les valeurs possibles, et en voici une autre!
Tom Hawtin - tackline
8
À l'exception des valeurs qui sont explicitement annotées @Nullable, je considère que Comparer les constantes de gauche est un contre-motif car il répand l'ambiguïté sur les valeurs qui peuvent ou non être nulles. Les types énum ne devraient presque jamais l'être @Nullable, donc une valeur nulle serait une erreur de programmation; dans un tel cas, plus vous lancez NPE tôt, plus vous risquez de trouver votre erreur de programmation là où vous l'avez autorisée à être nulle. Donc, "la meilleure pratique ... que vous devez absolument suivre" est tout à fait erronée en ce qui concerne les types d'énumération.
Tobias
24
Comparer les constantes de gauche est une meilleure pratique comme le meilleur anglais du style Yoda.
maaartinus
58

Comme d' autres l' ont dit, à la fois ==et le .equals()travail dans la plupart des cas. La certitude de temps de compilation que vous ne comparez pas des types d'objets complètement différents que d'autres ont signalés est valide et bénéfique, mais le type particulier de bogue de comparaison d'objets de deux types de temps de compilation différents serait également trouvé par FindBugs (et probablement par Inspections de temps de compilation Eclipse / IntelliJ), donc le compilateur Java qui le trouve n'ajoute pas beaucoup de sécurité supplémentaire.

Toutefois:

  1. Le fait que ==jette jamais NPE dans mon esprit est un inconvénient de ==. Il ne devrait presque jamais y avoir besoin de enumtypes null, car tout état supplémentaire que vous souhaitez exprimer via nullpeut simplement être ajouté à l' enuminstance supplémentaire. Si cela se produit de manière inattendue null, je préfère avoir un NPE que d' ==évaluer silencieusement à faux. Par conséquent, je ne suis pas d'accord avec l' opinion qu'il est plus sûr au moment de l'exécution ; il vaut mieux prendre l'habitude de ne jamais laisser de enumvaleurs @Nullable.
  2. L'argument qui ==est plus rapide est également faux. Dans la plupart des cas, vous appellerez .equals()une variable dont le type de temps de compilation est la classe enum, et dans ces cas, le compilateur peut savoir que c'est la même chose que ==(car enumla equals()méthode d'un ne peut pas être remplacée) et peut optimiser l'appel de fonction un moyen. Je ne sais pas si le compilateur fait actuellement cela, mais si ce n'est pas le cas, et s'avère être un problème de performance dans Java dans l'ensemble, je préfère corriger le compilateur plutôt que 100 000 programmeurs Java modifient leur style de programmation en fonction de les caractéristiques de performance d'une version de compilateur particulière.
  3. enumssont des objets. Pour tous les autres types d'objets, la comparaison standard ne l'est .equals()pas ==. Je pense qu'il est dangereux de faire une exception enumscar vous pourriez finir par comparer accidentellement des objets avec ==au lieu de equals(), surtout si vous refactorisez un enumen une classe non énumérée. Dans le cas d'une telle refactorisation, le point Ça marche d'en haut est faux. Pour vous convaincre qu'une utilisation de ==est correcte, vous devez vérifier si la valeur en question est une enumou une primitive; s'il s'agissait d'une non- enumclasse, ce serait faux mais facile à manquer car le code serait toujours compilé. Le seul cas où une utilisation de.equals()serait erroné si les valeurs en question étaient primitives; dans ce cas, le code ne serait pas compilé, il est donc beaucoup plus difficile à manquer. Par conséquent, il .equals()est beaucoup plus facile de l'identifier comme étant correct et il est plus sûr contre les remaniements futurs.

En fait, je pense que le langage Java aurait dû définir == sur les objets pour appeler .equals () sur la valeur de gauche, et introduire un opérateur distinct pour l'identité de l'objet, mais ce n'est pas ainsi que Java a été défini.

En résumé, je pense toujours que les arguments sont en faveur de l'utilisation .equals()pour les enumtypes.

Tobias
la source
4
Eh bien, au sujet du point 3, je peux l'utiliser enumcomme switchcible, donc ils sont un peu spéciaux. Strings sont un peu spéciaux aussi.
CurtainDog
La syntaxe des instructions switch ne contient ni == ni .equals () donc je ne vois pas quel est votre point. Mon point 3.) concerne la facilité de vérification du code pour un lecteur. La sémantique d'égalité des instructions switch est correcte tant que l'instruction switch est compilée.
Tobias
17

Je préfère utiliser ==plutôt que equals:

Une autre raison, en plus des autres déjà discutées ici, est que vous pourriez introduire un bogue sans vous en rendre compte. Supposons que vous ayez ces énumérations qui sont exactement les mêmes mais dans des pacakges séparés (ce n'est pas courant, mais cela pourrait arriver):

Première énumération :

package first.pckg

public enum Category {
    JAZZ,
    ROCK,
    POP,
    POP_ROCK
}

Deuxième énumération:

package second.pckg

public enum Category {
    JAZZ,
    ROCK,
    POP,
    POP_ROCK
}

Supposez ensuite que vous utilisez les égaux comme suivant dans item.categorylequel se trouve first.pckg.Categorymais que vous importez le deuxième enum ( second.pckg.Category) à la place du premier sans vous en rendre compte:

import second.pckg.Category;
...

Category.JAZZ.equals(item.getCategory())

Donc , vous obtiendrez AllWays à falsecause est un autre ENUM bien que vous attendez vrai parce que item.getCategory()est JAZZ. Et cela pourrait être un peu difficile à voir.

Donc, si vous utilisez plutôt l'opérateur, ==vous aurez une erreur de compilation:

L'opérateur == ne peut pas être appliqué à "second.pckg.Category", "first.pckg.Category"

import second.pckg.Category; 
...

Category.JAZZ == item.getCategory() 
Pau
la source
Très bons points que vous avez mentionnés. Donc, ce que je ferais, c'est d'appliquer myenum.value () puis de faire la comparaison == pour la sécurité NPE.
Java Main
14

Voici un test de synchronisation brut pour comparer les deux:

import java.util.Date;

public class EnumCompareSpeedTest {

    static enum TestEnum {ONE, TWO, THREE }

    public static void main(String [] args) {

        Date before = new Date();
        int c = 0;

        for(int y=0;y<5;++y) {
            for(int x=0;x<Integer.MAX_VALUE;++x) {
                if(TestEnum.ONE.equals(TestEnum.TWO)) {++c;}
                if(TestEnum.ONE == TestEnum.TWO){++c;}              
            }
        }

        System.out.println(new Date().getTime() - before.getTime());
    }   

}

Commentez les FI une par une. Voici les deux comparaisons ci-dessus en octet-code démonté:

 21  getstatic EnumCompareSpeedTest$TestEnum.ONE : EnumCompareSpeedTest.TestEnum [19]
 24  getstatic EnumCompareSpeedTest$TestEnum.TWO : EnumCompareSpeedTest.TestEnum [25]
 27  invokevirtual EnumCompareSpeedTest$TestEnum.equals(java.lang.Object) : boolean [28]
 30  ifeq 36

 36  getstatic EnumCompareSpeedTest$TestEnum.ONE : EnumCompareSpeedTest.TestEnum [19]
 39  getstatic EnumCompareSpeedTest$TestEnum.TWO : EnumCompareSpeedTest.TestEnum [25]
 42  if_acmpne 48

Le premier (égal) effectue un appel virtuel et teste le retour booléen de la pile. Le second (==) compare les adresses des objets directement à partir de la pile. Dans le premier cas, il y a plus d'activité.

J'ai effectué ce test plusieurs fois avec les deux FI, un à la fois. Le "==" est toujours un peu plus rapide.

ChrisCantrell
la source
Je soupçonne que la prédiction de branche du compilateur pourrait rendre ce test invalide. Un compilateur intelligent reconnaîtrait que ces deux instructions if seront toujours évaluées à false et éviteront ainsi de faire des comparaisons multiples. Un compilateur vraiment intelligent pourrait même reconnaître que les valeurs dans les boucles y et x n'auront aucun effet sur le résultat et optimiseront le tout ...
Darrel Hoffman
C'est possible, mais ce n'est certainement pas le cas. Je montre le bytecode généré par le compilateur dans ma réponse.
ChrisCantrell
vous êtes tous les deux confus. :-) La prédiction de branche fonctionne au moment de l'exécution, donc les différents codes d'octets ne sont pas super pertinents. Cependant, dans ce cas, la prédiction de branche aiderait les deux cas également.
vidstige
7
On s'en fout? À moins que vous ne compariez des énumérations des milliers de fois dans une boucle pendant un jeu vidéo, la nanoseconde supplémentaire n'a aucun sens.
user949300
Le bytecode n'est pas pertinent pour les performances, sauf si vous forcez le mode interprété. Écrivez un benchmark JMH pour voir que les performances sont exactement les mêmes.
maaartinus
10

En cas d'énumération, les deux sont corrects et corrects !!

Suraj Chandran
la source
7

tl; dr

Une autre option est la Objects.equalsméthode utilitaire.

Objects.equals( thisEnum , thatEnum )

Objects.equals pour une sécurité nulle

est égal à l'opérateur == au lieu de .equals ()

Quel opérateur dois-je utiliser?

Une troisième option est la equalsméthode statique trouvée sur la Objectsclasse d'utilitaires ajoutée à Java 7 et versions ultérieures.

Exemple

Voici un exemple utilisant l' Monthénumération.

boolean areEqual = Objects.equals( Month.FEBRUARY , Month.JUNE ) ;  // Returns `false`.

Avantages

Je trouve quelques avantages à cette méthode:

  • Sécurité nulle
  • Compact, lisible

Comment ça fonctionne

Quelle est la logique utilisée par Objects.equals?

Voyez par vous-même, à partir du code source Java 10 d' OpenJDK :

return (a == b) || (a != null && a.equals(b));
Basil Bourque
la source
6

Utiliser autre chose que ==de comparer des constantes enum est un non-sens. C'est comme comparer des classobjets avecequals - ne le faites pas!

Cependant, il y avait un bogue désagréable (BugId 6277781 ) dans Sun JDK 6u10 et versions antérieures qui pouvait être intéressant pour des raisons historiques. Ce bogue a empêché une utilisation correcte de ==sur les énumérations désérialisées, bien qu'il s'agisse sans doute d'un cas d'angle.

Tomas
la source
4

Les énumérations sont des classes qui renvoient une instance (comme des singletons) pour chaque constante d'énumération déclarée par public static final field(immuable) afin que l' ==opérateur puisse être utilisé pour vérifier leur égalité plutôt que d'utiliser la equals()méthode

ΦXocę 웃 Пepeúpa ツ
la source
1

La raison pour laquelle les énumérations fonctionnent facilement avec == est que chaque instance définie est également un singleton. La comparaison d'identité à l'aide de == fonctionnera donc toujours.

Mais utiliser == parce qu'il fonctionne avec des énumérations signifie que tout votre code est étroitement associé à l'utilisation de cette énumération.

Par exemple: les énumérations peuvent implémenter une interface. Supposons que vous utilisez actuellement une énumération qui implémente Interface1. Si plus tard, quelqu'un la modifie ou introduit une nouvelle classe Impl1 en tant qu'implémentation de la même interface. Ensuite, si vous commencez à utiliser des instances d'Impl1, vous aurez beaucoup de code à modifier et à tester en raison de l'utilisation précédente de ==.

Par conséquent, il est préférable de suivre ce qui est considéré comme une bonne pratique, sauf s'il y a un gain justifiable.

Dev Amitabh
la source
Belle réponse, mais déjà mentionnée ici .
shmosel
Hum. À ce stade, la question serait de savoir si vous utilisez == ou égal sur une interface. Plus toutes sortes de questions sur la pertinence de remplacer une implémentation d'un type immuable par une implémentation mutable. Et il est probablement sage de déterminer ce qu'est une bonne pratique avant de vous inquiéter d'un gain justifiable. (à mon humble avis == est une meilleure pratique).
Robin Davies
1

L'une des règles du sondeur est Enum values should be compared with "==". Les raisons sont les suivantes:

Tester l'égalité d'une valeur d'énumération avec equals()est parfaitement valide car une énumération est un objet et chaque développeur Java sait ==qu'il ne doit pas être utilisé pour comparer le contenu d'un objet. En même temps, en utilisant ==sur les énumérations:

  • fournit la même comparaison attendue (contenu) que equals()

  • est plus sûr que nul equals()

  • fournit une vérification à la compilation (statique) plutôt qu'une vérification à l'exécution

Pour ces raisons, l'utilisation de ==devrait être préférée à equals().

Enfin, ==on enums est sans doute plus lisible (moins verbeux) que equals().

Mohsen Zamani
la source
0

Bref, les deux ont des avantages et des inconvénients.

D'une part, il présente des avantages à utiliser ==, comme décrit dans les autres réponses.

D'un autre côté, si pour une raison quelconque, vous remplacez les énumérations par une approche différente (instances de classe normales), après avoir utilisé des ==morsures. (BTDT.)

glglgl
la source
-1

Je veux compléter la réponse des polygénelubrifiants:

Personnellement, je préfère equals (). Mais c'est la vérification de compatibilité des types. Je pense que c'est une limitation importante.

Pour faire vérifier la compatibilité des types au moment de la compilation, déclarez et utilisez une fonction personnalisée dans votre énumération.

public boolean isEquals(enumVariable) // compare constant from left
public static boolean areEqual(enumVariable, enumVariable2) // compare two variable

Avec cela, vous avez tous les avantages des deux solutions: protection NPE, code facile à lire et vérification de la compatibilité des types au moment de la compilation.

Je recommande également d'ajouter une valeur UNDEFINED pour enum.

Christ
la source
4
Pourquoi cette solution est-elle meilleure que ==? Avec ==vous bénéficiez d'une protection NPE, d'un code facile à lire et d'une vérification de la compatibilité des types de temps, et vous obtenez tout cela sans écrire de méthodes personnalisées de type égal.
Matt Ball
1
Une UNDEFINEDvaleur est probablement une bonne idée. Bien sûr, dans Java 8, vous utiliseriez Optionalplutôt un .
Tomas