Comparación de miembros de enumeración de Java: == o igual ()?

1736

Sé que las enumeraciones de Java se compilan en clases con constructores privados y un grupo de miembros estáticos públicos. Al comparar dos miembros de una enumeración dada, siempre he usado .equals(), por ejemplo

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

Sin embargo, acabo de encontrar un código que usa el operador igual en ==lugar de .equals ():

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

¿Qué operador es el que debería usar?

Matt Ball
fuente
77
Me topé con una pregunta muy similar: stackoverflow.com/questions/533922/…
Matt Ball
39
Me sorprende que en todas las respuestas (especialmente la de los poligenelubricantes que explica en detalle por qué == funciona) no se mencionó otro gran beneficio de ==: que explique cómo funcionan las enumeraciones (como un conjunto fijo de singleton objetos). Con iguales, lleva a pensar que de alguna manera puede haber múltiples instancias de la misma enumeración 'alternativa' flotando.
Stuart Rossiter
66
Mantenlo simple. "==" es más legible y funciona porque las enumeraciones son singleton por defecto. Ref
Lokesh

Respuestas:

1578

Ambas son técnicamente correctas. Si observa el código fuente .equals(), simplemente difiere ==.

Lo uso ==, sin embargo, ya que será nulo seguro.

Reverendo Gonzo
fuente
64
Personalmente, no me gusta la "seguridad nula" mencionada como una razón para usar ==.
Nivas el
258
@Nivas: ¿por qué no? ¿Le gusta preocuparse por el orden de sus argumentos (que solo se aplica a la comparación de constantes desde la izquierda, como en la respuesta de Pascal)? ¿Te gusta comprobar siempre que un valor no es nulo antes de .equals()usarlo? Yo se que no.
Matt Ball
86
Tenga en cuenta que al leer su código, el "==" puede parecer incorrecto para el lector hasta que se apaga y mira la fuente del tipo involucrado, luego ve que es una enumeración. En ese sentido, es menos molesto leer ".equals ()".
Kevin Bourrillion
6060
@Kevin: si no está utilizando un IDE que le permite ver los tipos directamente al ver la fuente, se está engañando a sí mismo, no es un problema con un IDE adecuado.
MetroidFan2002
63
A veces, el código se refactoriza y es posible que una enumeración se reemplace por una clase, en cuyo punto == ya no se garantiza que sea correcto, mientras que igual sigue funcionando sin problemas. Por esta razón, igual es claramente una mejor opción. Si desea una seguridad nula (porque trabaja en una base de código que no se escribió para minimizar el uso de nulos), hay Apache ObjectUtils u Guava's Objects # igual (o puede rodar el suyo). Ni siquiera mencionaré la palabra "rendimiento" por temor a que alguien me golpee legítimamente con un libro de Donald Knuth.
laszlok
1113

Se ==puede utilizar en enum?

Sí: las enumeraciones tienen controles de instancia ajustados que le permiten usar ==para comparar instancias. Aquí está la garantía proporcionada por la especificación del idioma (énfasis por mí):

JLS 8.9 Enums

Un tipo de enumeración no tiene instancias distintas de las definidas por sus constantes de enumeración.

Es un error en tiempo de compilación intentar crear una instancia explícita de un tipo de enumeración. El final clonemétodo Enumasegura que las enumconstantes nunca se puedan clonar, y el tratamiento especial por el mecanismo de serialización asegura que nunca se creen instancias duplicadas como resultado de la deserialización. Se prohíbe la creación de instancias reflexivas de tipos de enumeración. Juntas, estas cuatro cosas aseguran que no existan instancias de un enumtipo más allá de las definidas por las enumconstantes.

Debido a que solo hay una instancia de cada enumconstante, está permitido usar el ==operador en lugar del equalsmétodo al comparar dos referencias de objeto si se sabe que al menos una de ellas se refiere a una enumconstante . (El equalsmétodo en Enumes un finalmétodo que simplemente invoca super.equalssu argumento y devuelve el resultado, realizando así una comparación de identidad).

Esta garantía es lo suficientemente sólida como para que Josh Bloch recomiende que, si insiste en usar el patrón singleton, la mejor manera de implementarlo es usar un elemento único enum(consulte: Java 2nd Edition, elemento 3: aplicar la propiedad singleton con un constructor privado o un tipo de enumeración ; también seguridad de subprocesos en Singleton )


¿Cuáles son las diferencias entre ==y equals?

Como recordatorio, hay que decir que, en general, ==NO es una alternativa viable para equals. Sin embargo, cuando es así (como con enum), hay dos diferencias importantes a considerar:

== nunca tira NullPointerException

enum Color { BLACK, WHITE };

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

== está sujeto a verificación de compatibilidad de tipos en tiempo de compilación

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!

¿Debe ==usarse cuando corresponda?

Bloch menciona específicamente que las clases inmutables que tienen un control adecuado sobre sus instancias pueden garantizar a sus clientes que ==es utilizable. enumse menciona específicamente para ejemplificar.

Elemento 1: Considere métodos de fábrica estáticos en lugar de constructores

[...] permite que una clase inmutable garantice que no existen dos instancias iguales: a.equals(b)si y solo si a==b. Si una clase hace esta garantía, sus clientes pueden usar el ==operador en lugar del equals(Object)método, lo que puede mejorar el rendimiento. Los tipos de enumeración proporcionan esta garantía.

Para resumir, los argumentos para usar ==en enumson:

  • Funciona.
  • Es mas rapido.
  • Es más seguro en tiempo de ejecución.
  • Es más seguro en tiempo de compilación.
poligenelubricantes
fuente
27
Buena respuesta, pero ... 1. Funciona : de acuerdo 2. es más rápido : probarlo para enumeraciones :) 3. Es más seguro en tiempo de ejecución : en Color nothing = null;mi humilde opinión es un error y debe corregirse, su enumeración tiene dos valores, no tres ( vea el comentario de Tom Hawtin) 4. Es más seguro en tiempo de compilación : OK, pero equalsaún así volvería false. Al final, yo no lo creo, prefiero equals()para la lectura (ver el comentario de Kevin).
Pascal Thivent
201
Es un punto negro en contra de Java que algunas personas piensan que foo.equals(bar)es más fácil de leer quefoo == bar
Bennett McElwee
19
Funciona: hasta que necesite reemplazar una enumeración con una clase como parte de una refactorización. Buena suerte encontrando todo el código usando == que se rompe. Es más rápido: incluso si es cierto (cuestionable), existe esa cita de Knuth sobre la optimización prematura. Más seguro en tiempo de ejecución: "Seguro" no es la primera palabra que me viene a la mente si el uso de == es lo único que lo separa de una NullPointerException. Más seguro en tiempo de compilación: en realidad no. Puede protegerte del error más bien elemental de comparar los tipos incorrectos, pero de nuevo, si así de tontos son tus programadores, "seguro" es lo único que no eres.
laszlok
35
"si así de tontos son tus programadores," seguro "es lo único que no eres". - No estoy de acuerdo con esta cita. De esto se trata la verificación de tipos: evitar errores "tontos" en el momento de la compilación, por lo tanto, incluso antes de que se ejecute el código. Incluso las personas inteligentes cometen errores tontos, mejor algunas garantías que promesas.
ymajoros
77
@laszlok: claro, pero si las cosas pueden fallar, simplemente lo harán. Tener una cerca por algunos errores tontos siempre es algo bueno. Hay mucho espacio para ser creativo para errores menos triviales, deje que el compilador se encargue de esto antes de intentar ejecutar su código.
ymajoros
88

Usar ==para comparar dos valores de enumeración funciona, porque solo hay un objeto para cada constante de enumeración.

En una nota al margen, en realidad no hay necesidad de usar ==para escribir código nulo-seguro, si escribe equals()así:

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

Esta es una práctica recomendada conocida como Comparar constantes desde la izquierda que definitivamente debes seguir.

Pascal Thivent
fuente
99
Hago esto cuando .equals()ingiero valores de cadena, pero sigo pensando que es una forma horrible de escribir código nulo seguro. Prefiero tener un operador (o un método, pero sé que [objeto nulo] .equals nunca será nulo seguro en Java debido a cómo funciona el operador de punto) que siempre es nulo seguro, independientemente del orden en que se utiliza. Semánticamente, el operador "igual" siempre debe conmutar.
Matt Ball
2
Bueno, como Java no tiene el operador de Navegación segura de Groovy ( ?.), continuaré usando esta práctica al comparar con constantes y, TBH, no me parece horrible, tal vez simplemente menos "natural".
Pascal Thivent
32
Una nullenumeración suele ser un error. ¡Tienes esta enumeración de todos los valores posibles, y aquí hay otro!
Tom Hawtin - tackline el
8
A excepción de los valores que se anotan explícitamente como @Nullable, considero que Comparar constantes desde la izquierda es un antipatrón porque propaga la ambigüedad sobre qué valores pueden o no ser nulos. Los tipos de enumeración casi nunca deberían existir @Nullable, por lo que un valor nulo sería un error de programación; en tal caso, cuanto antes arroje NPE, más probable será que encuentre su error de programación donde permitió que sea nulo. Entonces, "la mejor práctica ... que definitivamente debes seguir" es completamente errónea cuando se trata de tipos de enumeración.
Tobias
24
Compare Constants From The Left, una práctica recomendada como el estilo Yoda, el mejor inglés.
maaartinus
58

Como han dicho otros, tanto ==y .equals()de trabajo en la mayoría de los casos. La certeza de tiempo de compilación de que no está comparando tipos completamente diferentes de Objetos que otros han señalado es válida y beneficiosa, sin embargo, FindBugs también encontraría el tipo particular de error de comparar objetos de dos tipos de tiempo de compilación diferentes (y probablemente por Eclipse / IntelliJ inspecciones de tiempo de compilación), por lo que el compilador de Java al encontrarlo no agrega tanta seguridad adicional.

Sin embargo:

  1. El hecho de que ==nunca tira NPE en mi mente es una desventaja de ==. Casi nunca debería existir la necesidad de enumque nullexistan tipos , ya que cualquier estado adicional que desee expresar a través de él nullpuede agregarse enumcomo una instancia adicional. Si es inesperado null, prefiero tener un NPE que ==evaluar en silencio a falso. Por lo tanto, no estoy de acuerdo con la opinión más segura en tiempo de ejecución ; es mejor acostumbrarse a nunca dejar que los enumvalores sean @Nullable.
  2. El argumento que ==es más rápido también es falso. En la mayoría de los casos se le llama .equals()en una variable cuyo tipo de tiempo de compilación es la clase de enumeración, y en esos casos el compilador puede saber que esto es lo mismo que ==(debido a una enum's equals()método no puede ser anulado) y se puede optimizar la función de llamada lejos. No estoy seguro de si el compilador actualmente hace esto, pero si no lo hace, y resulta ser un problema de rendimiento en Java en general, prefiero arreglar el compilador antes de que 100,000 programadores de Java cambien su estilo de programación para adaptarse características de rendimiento de una versión de compilador particular.
  3. enumsson objetos Para todos los demás tipos de Objetos, la comparación estándar es .equals(), no ==. Creo que es peligroso hacer una excepción enumsporque podría terminar comparando accidentalmente objetos con en ==lugar de equals(), especialmente si refactoriza una enumclase que no es enum. En caso de tal refactorización, el punto Funciona desde arriba es incorrecto. Para convencerse de que el uso de ==es correcto, debe verificar si el valor en cuestión es enumprimitivo o no; si no fuera de enumclase, sería incorrecto pero fácil de perder porque el código aún se compilaría. El único caso cuando un uso de.equals()estaría mal si los valores en cuestión fueran primitivos; en ese caso, el código no se compilaría, por lo que es mucho más difícil pasarlo por alto. Por lo tanto, .equals()es mucho más fácil de identificar como correcto y es más seguro contra futuras refactorizaciones.

De hecho, creo que el lenguaje Java debería haber definido == en Objetos para llamar a .equals () en el valor de la izquierda e introducir un operador separado para la identidad del objeto, pero no es así como se definió Java.

En resumen, sigo pensando que los argumentos están a favor del uso .equals()de enumtipos.

Tobias
fuente
44
Bueno, sobre el punto 3, puedo usarlo enumcomo switchobjetivo, por lo que son un poco especiales. Strings son un poco especiales también.
CurtainDog
La sintaxis para las declaraciones de cambio no contiene ni == ni .equals (), por lo que no veo cuál es su punto. Mi punto 3.) trata sobre lo fácil que es verificar el código para un lector. La semántica de igualdad de las declaraciones de cambio es correcta siempre que se compile la declaración de cambio.
Tobias
17

Prefiero usar en ==lugar de equals:

Otra razón, además de las otras ya discutidas aquí, es que podría introducir un error sin darse cuenta. Supongamos que tiene estas enumeraciones que son exactamente iguales pero en paquetes separados (no es común, pero podría suceder):

Primera enumeración :

package first.pckg

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

Segunda enumeración:

package second.pckg

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

Luego, suponga que usa los iguales como next en el item.categoryque está first.pckg.Categorypero importa el segundo enum ( second.pckg.Category) en lugar del primero sin darse cuenta:

import second.pckg.Category;
...

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

Así que siempre obtendrás falseuna enumeración diferente, aunque esperas cierto porqueitem.getCategory() es así JAZZ. Y podría ser un poco difícil de ver.

Entonces, si en su lugar usa el operador == , tendrá un error de compilación:

El operador == no se puede aplicar a "second.pckg.Category", "first.pckg.Category"

import second.pckg.Category; 
...

Category.JAZZ == item.getCategory() 
Pau
fuente
Muy buenos puntos que mencionaste. Entonces, lo que haría es aplicar myenum.value () y luego hacer la comparación == para la seguridad de NPE.
Java Main
14

Aquí hay una prueba cruda de tiempo para comparar los dos:

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

}

Comente los IF uno por uno. Aquí están las dos comparaciones de arriba en código de byte desmontado:

 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

El primero (igual) realiza una llamada virtual y prueba el valor booleano de retorno de la pila. El segundo (==) compara las direcciones de los objetos directamente desde la pila. En el primer caso hay más actividad.

Ejecuté esta prueba varias veces con ambos IF uno a la vez. El "==" es un poco más rápido.

ChrisCantrell
fuente
Sospecho que la predicción de ramificación del compilador podría invalidar esta prueba. Un compilador inteligente reconocería que ambas sentencias if siempre se evaluarán como falsas y, por lo tanto, evitarán hacer comparaciones múltiples. Un compilador realmente inteligente podría incluso reconocer que los valores en los bucles y y x no tendrán ningún efecto en el resultado y optimizarán todo ...
Darrel Hoffman
Podría, pero definitivamente no. Muestro el bytecode generado por el compilador real en mi respuesta.
ChrisCantrell
Los dos están confundidos. :-) La predicción de bifurcación funciona en tiempo de ejecución, por lo que los diferentes códigos de bytes no son muy relevantes. Sin embargo, en este caso, la predicción de rama ayudaría a ambos casos por igual.
vidstige
77
¿A quien le importa? A menos que esté comparando enumeraciones miles de veces dentro de un ciclo durante un videojuego, el nanosegundo extra no tiene sentido.
user949300
El bytecode es bastante irrelevante para el rendimiento a menos que esté forzando el modo interpretado. Escriba un punto de referencia JMH para ver que el rendimiento es exactamente el mismo.
maaartinus
10

¡En caso de enumeración, ambos son correctos y correctos!

Suraj Chandran
fuente
7

tl; dr

Otra opción es el Objects.equalsmétodo de utilidad.

Objects.equals( thisEnum , thatEnum )

Objects.equals para seguridad nula

operador igual == en lugar de .equals ()

¿Qué operador es el que debería usar?

Una tercera opción es el equalsmétodo estático que se encuentra en la Objectsclase de utilidad agregada a Java 7 y versiones posteriores.

Ejemplo

Aquí hay un ejemplo usando la Monthenumeración.

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

Beneficios

Encuentro un par de beneficios para este método:

  • Seguridad nula
  • Compacto, legible

Cómo funciona

¿Cuál es la lógica utilizada por Objects.equals?

Véalo usted mismo, del código fuente de Java 10 de OpenJDK :

return (a == b) || (a != null && a.equals(b));
Albahaca Bourque
fuente
6

Usar cualquier otra cosa que no sea ==comparar constantes enum no tiene sentido. Es como comparar classobjetos conequals , ¡no lo hagas!

Sin embargo, hubo un error desagradable (BugId 6277781 ) en Sun JDK 6u10 y anteriores que podría ser interesante por razones históricas. Este error evitó el uso adecuado de las ==enumeraciones deserializadas, aunque esto podría decirse que es un caso de esquina.

Tomás
fuente
4

Las enumeraciones son clases que devuelven una instancia (como singletons) para cada constante de enumeración declarada por public static final field(inmutable) para que el ==operador pueda usarse para verificar su igualdad en lugar de usar el equals()método

ΦXocę 웃 Пepeúpa ツ
fuente
1

La razón por la que las enumeraciones funcionan fácilmente con == es porque cada instancia definida también es un singleton. Entonces la comparación de identidad usando == siempre funcionará.

Pero usar == porque funciona con enumeraciones significa que todo su código está estrechamente relacionado con el uso de esa enumeración.

Por ejemplo: las enumeraciones pueden implementar una interfaz. Suponga que actualmente está utilizando una enumeración que implementa Interface1. Si más adelante, alguien lo cambia o introduce una nueva clase Impl1 como implementación de la misma interfaz. Luego, si comienza a usar instancias de Impl1, tendrá mucho código para cambiar y probar debido al uso previo de ==.

Por lo tanto, es mejor seguir lo que se considera una buena práctica a menos que haya una ganancia justificable.

Dev Amitabh
fuente
Buena respuesta, pero ya mencioné aquí .
shmosel
Um. En ese punto, la pregunta sería si usa == o igual en una interfaz. Además de todo tipo de preguntas sobre si es realmente sensato reemplazar una implementación de un tipo inmutable con una implementación mutable. Y probablemente sea aconsejable averiguar qué es una buena práctica antes de preocuparse por una ganancia justificable. (imho == es una mejor práctica).
Robin Davies
1

Una de las reglas de Sonar es Enum values should be compared with "==". Las razones son las siguientes:

Probar la igualdad de un valor de enumeración con equals()es perfectamente válido porque una enumeración es un Objeto y todo desarrollador Java sabe ==que no debe usarse para comparar el contenido de un Objeto. Al mismo tiempo, usando ==en enumeraciones:

  • proporciona la misma comparación esperada (contenido) que equals()

  • es más nulo seguro que equals()

  • proporciona comprobación en tiempo de compilación (estática) en lugar de comprobación en tiempo de ejecución

Por estas razones, el uso de == debe preferir el uso de equals().

Por último, pero no menos importante, las ==enumeraciones on son posiblemente más legibles (menos detalladas) que equals().

Mohsen Zamani
fuente
0

En resumen, ambos tienen pros y contras.

Por un lado, tiene ventajas para usar == , como se describe en las otras respuestas.

Por otro lado, si por alguna razón reemplaza las enumeraciones con un enfoque diferente (instancias de clase normales), habiendo usado ==picaduras. (BTDT.)

glglgl
fuente
-1

Quiero complementar la respuesta de polygenelubricants:

Personalmente prefiero igual (). Pero tiene el control de compatibilidad de tipos. Lo cual creo que es una limitación importante.

Para comprobar la compatibilidad de tipos en el momento de la compilación, declare y use una función personalizada en su enumeración.

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

Con esto, tiene todas las ventajas de ambas soluciones: protección NPE, código fácil de leer y verificación de compatibilidad de tipos en el momento de la compilación.

También recomiendo agregar un valor INDEFINIDO para enum.

Cristo
fuente
44
¿Por qué esta solución es mejor que ==? Con ==usted obtiene protección NPE, código fácil de leer y compilación de verificación de compatibilidad de tipo de tiempo, y obtiene todo eso sin escribir ningún método personalizado similar a igual.
Matt Ball
1
Un UNDEFINEDvalor es probablemente una buena idea. Por supuesto, en Java 8 usarías un Optionallugar.
Tomás