Confronto tra membri di enum Java: == o equals ()?


1736

So che gli enum Java sono compilati in classi con costruttori privati ​​e un gruppo di membri statici pubblici. Quando ho confrontato due membri di un dato enum, l'ho sempre usato .equals(), ad es

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

Tuttavia, ho appena trovato un codice che utilizza l'operatore equals ==anziché .equals ():

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

Quale operatore è quello che dovrei usare?


7
Mi sono appena imbattuto in una domanda molto simile: stackoverflow.com/questions/533922/…
Matt Ball,

39
Sono sorpreso che in tutte le risposte (in particolare quella dei poligenelubrificanti che spiega in dettaglio perché == funziona) non sia stato menzionato un altro grande vantaggio di ==: che rende esplicito il funzionamento degli enum (come un insieme fisso di singleton oggetti). Con uguali porta a pensare che possano esserci in qualche modo più istanze della stessa enum 'alternativa' che fluttua intorno.
Stuart Rossiter,

6
Mantienilo semplice. "==" è più leggibile e funziona perché gli enum sono singleton per impostazione predefinita. Rif
lokesh,

Risposte:


1578

Entrambi sono tecnicamente corretti. Se guardi il codice sorgente per .equals(), semplicemente lo fa ==.

Uso ==, tuttavia, poiché sarà nullo e sicuro.


64
Personalmente, non mi piace la "sicurezza nulla" menzionata come motivo per usare ==.
Nivas,

258
@Nivas: perché no? Ti piace preoccuparti dell'ordine dei tuoi argomenti (che si applica solo al confronto delle costanti da sinistra, come nella risposta di Pascal)? Ti piace sempre verificare che un valore non sia nullo prima di .equals()inserirlo? So di no.
Matt Ball,

86
Tieni presente che quando leggi il tuo codice, "==" potrebbe apparire errato al lettore fino a quando non si spegne e cerca l'origine del tipo in questione, quindi vede che è un enum. In questo senso, è meno distratto leggere ".equals ()".
Kevin Bourrillion,

60
@Kevin: se non stai utilizzando un IDE che ti consente di vedere direttamente i tipi durante la visualizzazione della fonte, stai ingannando te stesso, non un problema con un IDE corretto.
MetroidFan2002,

63
A volte il codice viene refactored ed è possibile che un enum venga sostituito da una classe, a quel punto == non è più garantito che sia corretto, mentre uguali continua a funzionare senza problemi. Per questo motivo, uguale a è chiaramente una scelta migliore. Se vuoi la sicurezza nulla (perché lavori su una base di codice che non è stata scritta in modo da ridurre al minimo l'uso di valori null), c'è Apache ObjectUtils o Guava's Objects # uguale (o puoi rotolare il tuo). Non menzionerò nemmeno la parola "spettacolo" per paura che qualcuno mi schiaffeggi giustamente un libro di Donald Knuth.
Laszlok,

1113

Può ==essere usato su enum?

Sì: gli enum hanno controlli di istanza precisi che ti consentono ==di confrontare le istanze. Ecco la garanzia fornita dalla specifica della lingua (enfasi da me):

JLS 8.9 Enums

Un tipo enum non ha istanze diverse da quelle definite dalle sue costanti enum.

È un errore in fase di compilazione tentare di creare un'istanza esplicita di un tipo enum. Il final clonemetodo Enumgarantisce che le enumcostanti non possano mai essere clonate e il trattamento speciale con il meccanismo di serializzazione garantisce che istanze duplicate non vengano mai create a causa della deserializzazione. È vietata l'istanza riflessiva dei tipi di enum. Insieme, queste quattro cose assicurano che non esistano istanze di un enumtipo oltre a quelle definite dalle enumcostanti.

Poiché esiste solo un'istanza di ogni enumcostante, è possibile utilizzare l' ==operatore al posto del equalsmetodo quando si confrontano due riferimenti a oggetti se è noto che almeno uno di essi fa riferimento a una enumcostante . (Il equalsmetodo in Enumè un finalmetodo che si limita a invocare super.equalsil suo argomento e restituisce il risultato, eseguendo così un confronto di identità.)

Questa garanzia è abbastanza forte che Josh Bloch consiglia, se insisti nell'usare il modello singleton, il modo migliore per implementarlo è usare un singolo elemento enum(vedi: Effective Java 2nd Edition, Item 3: Enforce the singleton property with a costruttore privato o un tipo enum ; anche sicurezza thread in Singleton )


Quali sono le differenze tra ==e equals?

Come promemoria, va detto che generalmente ==NON è una valida alternativa a equals. Quando lo è, tuttavia (come con enum), ci sono due importanti differenze da considerare:

== non lancia mai NullPointerException

enum Color { BLACK, WHITE };

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

== è soggetto al controllo di compatibilità del tipo al momento della compilazione

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!

Dovrebbe ==essere usato quando applicabile?

Bloch menziona specificamente che le classi immutabili che hanno il controllo adeguato sulle loro istanze possono garantire ai loro clienti che ==è utilizzabile. enumè specificamente menzionato per esemplificare.

Articolo 1: considerare i metodi di fabbrica statici anziché i costruttori

[...] consente a una classe immutabile di garantire che non esistono due istanze uguali: a.equals(b)se e solo se a==b. Se una classe offre questa garanzia, i suoi clienti possono utilizzare l' ==operatore invece del equals(Object)metodo, il che può comportare un miglioramento delle prestazioni. I tipi Enum forniscono questa garanzia.

Per riassumere, gli argomenti per l'utilizzo di ==on enumsono:

  • Funziona.
  • È più veloce.
  • È più sicuro in fase di esecuzione.
  • È più sicuro in fase di compilazione.

27
Bella risposta ma ... 1. Funziona : d'accordo 2. È più veloce : dimostralo per gli enum :) 3. È più sicuro in fase di esecuzione : Color nothing = null;è IMHO un bug e dovrebbe essere corretto, il tuo enum ha due valori, non tre ( vedi il commento di Tom Hawtin) 4. È più sicuro in fase di compilazione : OK, ma equalsritornerebbe comunque false. Alla fine, io non compro, io preferisco equals()la leggibilità (si veda il commento di Kevin).
Pascal Thivent,

201
È un segno nero contro Java che alcune persone pensano che foo.equals(bar)sia più leggibile difoo == bar
Bennett McElwee,

19
Funziona: fino a quando non è necessario sostituire un enum con una classe come parte di un refactoring. Buona fortuna a trovare tutto il codice usando == che si rompe. È più veloce: anche se vero (discutibile), c'è quella citazione di Knuth sull'ottimizzazione prematura. Più sicuro in fase di esecuzione: "Sicuro" non è la prima parola che viene in mente se l'uso di == è l'unica cosa che ti separa da una NullPointerException. Più sicuro in fase di compilazione: non proprio. Può proteggerti dall'errore piuttosto elementare di confrontare i tipi sbagliati, ma ancora una volta, se è così stupido i tuoi programmatori, "sicuro" è l'unica cosa che non sei.
Laszlok,

35
"se è così stupido il tuo programmatore," sicuro "è l'unica cosa che non sei." - Non sono d'accordo con questa citazione. Questo è il controllo del tipo: prevenire errori "stupidi" in fase di compilazione, quindi anche prima dell'esecuzione del codice. Anche le persone intelligenti fanno errori stupidi, meglio alcune garanzie che promesse.
ymajoros,

7
@laszlok: certo, ma se le cose possono fallire, lo faranno. Avere una barriera per alcuni errori stupidi è sempre una buona cosa. C'è molto spazio per essere creativi per bug meno banali, lascia che il compilatore lo gestisca prima di provare a eseguire il tuo codice.
ymajoros,

88

L'uso ==per confrontare due valori di enum funziona, poiché esiste un solo oggetto per ogni costante di enum.

In una nota a margine, in realtà non è necessario utilizzare ==per scrivere codice null-safe, se si scrive in equals()questo modo:

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

Questa è una best practice nota come Confronta costanti da sinistra che dovresti assolutamente seguire.


9
Lo faccio quando si .equals()inseriscono i valori di stringa, ma penso ancora che sia un modo schifoso di scrivere codice null-safe. Preferirei avere un operatore (o un metodo, ma so che [null object] .equals non sarà mai null-safe in Java a causa di come funziona l'operatore punto) che è sempre null-safe, indipendentemente dal nell'ordine in cui è usato. Semanticamente, l'operatore "uguale" dovrebbe sempre spostarsi.
Matt Ball,

2
Bene, dato che Java non ha l'operatore di Navigazione sicura di Groovy ( ?.), continuerò a usare questa pratica quando confrontato con le costanti e, TBH, non lo trovo schifoso, forse solo meno "naturale".
Pascal Thivent,

32
Un nullenum è in genere un errore. Hai questa enumerazione di tutti i possibili valori, ed eccone un altro!
Tom Hawtin: affronta il

8
Fatta eccezione per i valori che sono esplicitamente annotati come @Nullable, considero Confronta Costanti da The Left un antipattern perché diffonde ambiguità su quali valori possono o non possono essere nulli. I tipi di enum non dovrebbero quasi mai esserlo @Nullable, quindi un valore nullo sarebbe un errore di programmazione; in tal caso, prima lanci l'NPE, più è probabile che troverai l'errore di programmazione in cui hai lasciato che sia nullo. Quindi "la migliore pratica ... che dovresti assolutamente seguire" è chiaramente sbagliata quando si tratta di tipi enum.
Tobias,

24
Confronta Costanti da sinistra una buona pratica come il miglior inglese in stile Yoda.
maaartinus,

58

Come altri hanno detto, sia ==e .equals()il lavoro nella maggior parte dei casi. La certezza del tempo di compilazione che non stai confrontando tipi di oggetti completamente diversi che altri hanno sottolineato è valida e vantaggiosa, tuttavia il particolare tipo di bug di confronto di oggetti di due diversi tipi di tempo di compilazione sarebbe trovato anche da FindBugs (e probabilmente da Ispezioni del tempo di compilazione Eclipse / IntelliJ), quindi il compilatore Java che lo trova non aggiunge molta sicurezza in più.

Però:

  1. Il fatto che ==non metta mai in mente NPE nella mia mente è uno svantaggio di ==. Difficilmente dovrebbe esserci la necessità che i enumtipi lo siano null, poiché ogni ulteriore stato che potresti voler esprimere tramite nullpuò essere aggiunto enumall'istanza aggiuntiva. Se è inaspettatamente null, preferirei avere un NPE piuttosto ==che valutare silenziosamente su falso. Pertanto non sono d'accordo con il fatto che sia più sicuro all'opinione di runtime ; è meglio prendere l'abitudine di non lasciare mai enumvalori @Nullable.
  2. L'argomento che ==è più veloce è anche falso. Nella maggior parte dei casi si chiama .equals()su una variabile il cui tipo momento della compilazione è la classe enum, e in quei casi il compilatore può sapere che questo è lo stesso ==(a causa di un enum's equals()metodo non può essere ignorato) e in grado di ottimizzare la chiamata di funzione lontano. Non sono sicuro che il compilatore attualmente lo faccia, ma in caso contrario, e risulta essere un problema di prestazioni in Java nel complesso, quindi preferirei correggere il compilatore piuttosto che 100.000 programmatori Java cambiano il loro stile di programmazione per adattarlo le caratteristiche prestazionali di una versione del compilatore.
  3. enumssono oggetti. Per tutti gli altri tipi di oggetti, il confronto standard è .equals()no ==. Penso che sia pericoloso fare un'eccezione enumsperché potresti finire per confrontare accidentalmente Oggetti ==invece di equals(), specialmente se fai il refactoring enumin una classe non enum. In caso di tale refactoring, il punto Funziona dall'alto è sbagliato. Per convincerti che un uso di ==è corretto, devi verificare se il valore in questione è o una enumo una primitiva; se non fosse una enumclasse, sarebbe sbagliato ma facile da perdere perché il codice sarebbe ancora compilato. L'unico caso in cui un uso di.equals()sarebbe sbagliato se i valori in questione fossero primitivi; in tal caso, il codice non verrà compilato, quindi è molto più difficile perdere. Quindi, .equals()è molto più facile da identificare come corretto ed è più sicuro contro future rifatturazioni.

In realtà penso che il linguaggio Java avrebbe dovuto definire == su Oggetti per chiamare .equals () sul valore a sinistra e introdurre un operatore separato per l'identità dell'oggetto, ma non è così che è stato definito Java.

In sintesi, penso ancora che gli argomenti siano a favore dell'uso .equals()per i enumtipi.


4
Bene, circa il punto 3, posso usare enumcome switchbersaglio quindi sono un po 'speciali. Stringsono anche un po 'speciali.
CurtainDog

La sintassi per le istruzioni switch non contiene né == né .equals (), quindi non vedo quale sia il tuo punto. Il mio punto 3.) riguarda quanto sia facile verificare il codice per un lettore. La semantica dell'uguaglianza delle istruzioni switch è corretta fintanto che l'istruzione switch viene compilata.
Tobias,

17

Preferisco usare ==invece di equals:

Un altro motivo, oltre agli altri già discussi qui, è che potresti introdurre un bug senza accorgertene. Supponiamo di avere questo enum che è esattamente lo stesso ma in pacakges separati (non è comune, ma potrebbe accadere):

Primo enum :

package first.pckg

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

Secondo enum:

package second.pckg

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

Quindi supponi di usare gli uguali come next in item.categorycui è first.pckg.Categoryma importa il secondo enum ( second.pckg.Category) invece il primo senza accorgertene:

import second.pckg.Category;
...

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

Quindi avrai sempre falsedovuto perché è un enum diverso anche se ti aspetti vero perché lo item.getCategory()è JAZZ. E potrebbe essere un po 'difficile da vedere.

Quindi, se invece usi l'operatore ==, avrai un errore di compilazione:

l'operatore == non può essere applicato a "second.pckg.Category", "first.pckg.Category"

import second.pckg.Category; 
...

Category.JAZZ == item.getCategory() 

Ottimi punti che hai menzionato. Quindi quello che vorrei fare è applay myenum.value () e quindi fare il comparaison == per la sicurezza NPE.
Java Main

14

Ecco un test di cronometraggio per confrontare i due:

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());
    }   

}

Commenta gli IF uno alla volta. Ecco i due confronti dall'alto nel codice byte disassemblato:

 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

Il primo (uguale a) esegue una chiamata virtuale e verifica il valore booleano di ritorno dallo stack. Il secondo (==) confronta gli indirizzi degli oggetti direttamente dallo stack. Nel primo caso c'è più attività.

Ho eseguito questo test più volte con entrambi gli IF uno alla volta. "==" è sempre leggermente più veloce.


Sospetto che la previsione del ramo del compilatore potrebbe rendere non valido questo test. Un compilatore intelligente riconoscerebbe che entrambe le istruzioni if ​​valuteranno sempre false e quindi eviterebbero confronti multipli. Un compilatore davvero intelligente potrebbe persino riconoscere che i valori nei cicli yey non avranno alcun effetto sul risultato e ottimizzeranno il tutto ...
Darrel Hoffman,

Potrebbe, ma sicuramente no. Mostro il codice byte generato dal compilatore nella mia risposta.
ChrisCantrell,

siete entrambi confusi. :-) La previsione del ramo funziona in fase di esecuzione, quindi il diverso codice byte non è super rilevante. Tuttavia, in questo caso la previsione del ramo aiuterebbe entrambi i casi allo stesso modo.
vidstige,

7
Che importa? A meno che non si stiano confrontando enumerazioni migliaia di volte all'interno di un loop durante un videogioco, il nanosecondo aggiuntivo non ha senso.
user949300

Il bytecode è piuttosto irrilevante per le prestazioni a meno che non si stia forzando la modalità interpretata. Scrivi un benchmark JMH per vedere che le prestazioni sono esattamente le stesse.
maaartinus,


7

tl; dr

Un'altra opzione è il Objects.equalsmetodo di utilità.

Objects.equals( thisEnum , thatEnum )

Objects.equals per sicurezza nulla

equals operator == anziché .equals ()

Quale operatore è quello che dovrei usare?

Una terza opzione è il equalsmetodo statico trovato nella Objectsclasse di utilità aggiunta a Java 7 e versioni successive.

Esempio

Ecco un esempio usando l' Monthenum.

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

Benefici

Trovo un paio di vantaggi per questo metodo:

  • Null-sicurezza
  • Compatto, leggibile

Come funziona

Qual è la logica utilizzata da Objects.equals?

Guarda tu stesso, dal codice sorgente Java 10 di OpenJDK :

return (a == b) || (a != null && a.equals(b));

6

Usare qualcosa di diverso da ==confrontare le costanti di enum è una sciocchezza. È come confrontare gli classoggetti conequals - non farlo!

Tuttavia, c'era un brutto bug (BugId 6277781 ) in Sun JDK 6u10 e precedenti che potrebbe essere interessante per motivi storici. Questo bug ha impedito un uso corretto degli ==enumeratori deserializzati, sebbene questo sia probabilmente un po 'un caso angolare.


4

Le enumerazioni sono classi che restituiscono un'istanza (come i singleton) per ogni costante di enumerazione dichiarata da public static final field(immutabile) in modo che l' ==operatore possa essere usato per verificare la propria uguaglianza piuttosto che usare il equals()metodo


1

Il motivo per cui enum funziona facilmente con == è perché ogni istanza definita è anche un singleton. Quindi il confronto delle identità usando == funzionerà sempre.

Ma usare == perché funziona con enum significa che tutto il tuo codice è strettamente associato all'utilizzo di quell'enum.

Ad esempio: Enums può implementare un'interfaccia. Supponiamo che tu stia attualmente utilizzando un enum che implementa Interface1. Se in seguito, qualcuno lo modifica o introduce una nuova classe Impl1 come implementazione della stessa interfaccia. Quindi, se inizi a utilizzare istanze di Impl1, avrai molto codice da modificare e testare a causa dell'utilizzo precedente di ==.

Quindi, è meglio seguire quella che è considerata una buona pratica a meno che non ci sia un guadagno giustificabile.


Bella risposta, ma già menzionata qui .
Shmosel,

Um. A quel punto, la domanda sarebbe se usi == o è uguale a un'interfaccia. Oltre a tutti i tipi di domande sul fatto che sia davvero ragionevole sostituire un'implementazione di un tipo immutabile con un'implementazione mutabile. Ed è probabilmente saggio capire quale sia la buona pratica prima di preoccuparsi di un guadagno giustificabile. (imho == è la migliore pratica).
Robin Davies,

1

Una delle regole del sonar è Enum values should be compared with "==". Le ragioni sono le seguenti:

Testare l'uguaglianza di un valore enum con equals()è perfettamente valido perché un enum è un oggetto e ogni sviluppatore Java sa ==che non dovrebbe essere usato per confrontare il contenuto di un oggetto. Allo stesso tempo, usando ==su enums:

  • fornisce lo stesso confronto atteso (contenuto) di equals()

  • è più sicuro di null di equals()

  • fornisce il controllo in fase di compilazione (statico) anziché il controllo di runtime

Per questi motivi, l'uso di ==dovrebbe essere preferito a equals().

Ultimo ma non meno importante, gli ==en on sono probabilmente più leggibili (meno dettagliati) di equals().


0

In breve, entrambi hanno pro e contro.

Da un lato, ha vantaggi da usare ==, come descritto nelle altre risposte.

D'altra parte, se per qualsiasi motivo sostituisci gli enum con un approccio diverso (istanze di classe normali), dopo aver usato i ==morsi. (BTDT.)


-1

Voglio integrare la risposta ai poligenilubrificanti:

Personalmente preferisco equals (). Ma verifica il controllo di compatibilità del tipo. Quale penso sia una limitazione importante.

Per verificare la compatibilità dei tipi al momento della compilazione, dichiarare e utilizzare una funzione personalizzata nel proprio enum.

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

Con questo, hai ottenuto tutti i vantaggi di entrambe le soluzioni: protezione NPE, codice di facile lettura e controllo della compatibilità dei tipi al momento della compilazione.

Consiglio anche di aggiungere un valore UNDEFINED per enum.


4
Perché questa soluzione è migliore di ==? Con la ==protezione NPE, il codice di facile lettura e la verifica della compatibilità del tipo di tempo di compilazione, è possibile ottenere tutti questi elementi senza scrivere metodi uguali personalizzati.
Matt Ball,

1
Un UNDEFINEDvalore è probabilmente una buona idea. Ovviamente, in Java 8 useresti Optionalinvece un .
Tomas,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.