¿Está bien usar == en enumeraciones en Java?

111

¿Está bien usar ==en enumeraciones en Java, o necesito usarlo .equals()? En mis pruebas, ==siempre funciona, pero no estoy seguro si tengo la garantía de eso. En particular, no hay un .clone()método en una enumeración, por lo que no sé si es posible obtener una enumeración para la que .equals()devolvería un valor diferente a ==.

Por ejemplo, esto está bien:

public int round(RoundingMode roundingMode) {
  if(roundingMode == RoundingMode.HALF_UP) {
    //do something
  } else if (roundingMode == RoundingMode.HALF_EVEN) {
    //do something
  }
  //etc
}

O necesito escribirlo de esta manera:

public int round(RoundingMode roundingMode) {
  if(roundingMode.equals(RoundingMode.HALF_UP)) {
    //do something
  } else if (roundingMode.equals(RoundingMode.HALF_EVEN)) {
    //do something
  }
  //etc
}
Dormir
fuente
@assylias esta pregunta fue lo primero. Quizás llamar la atención, ya que no estoy seguro de si los dos deberían fusionarse.
Matt Ball
@MattBall Creo que la respuesta a su pregunta que cita el JLS es la mejor respuesta, por eso elegí cerrar esta.
Assylias

Respuestas:

149

Solo mis 2 centavos: aquí está el código de Enum.java, publicado por Sun, y parte del JDK:

public abstract class Enum<E extends Enum<E>>
    implements Comparable<E>, Serializable {

    // [...]

    /**
     * Returns true if the specified object is equal to this
     * enum constant.
     *
     * @param other the object to be compared for equality with this object.
     * @return  true if the specified object is equal to this
     *          enum constant.
     */
    public final boolean equals(Object other) { 
        return this==other;
    }


}
Varkhan
fuente
4
¡Gracias! Supongo que si hubiera pensado en entrar en .equals () con el compilador, habría visto esto ...
Kip
77

Sí, == está bien, se garantiza que habrá una única referencia para cada valor.

Sin embargo, hay una mejor manera de escribir su método de ronda:

public int round(RoundingMode roundingMode) {
  switch (roundingMode) {
    case HALF_UP:
       //do something
       break;
    case HALF_EVEN:
       //do something
       break;
    // etc
  }
}

Una forma aún mejor de hacerlo es poner la funcionalidad dentro de la enumeración, por lo que podría simplemente llamar roundingMode.round(someValue). Esto llega al corazón de las enumeraciones de Java: son enumeraciones orientadas a objetos, a diferencia de los "valores con nombre" que se encuentran en otros lugares.

EDITAR: La especificación no es muy clara, pero la sección 8.9 establece:

El cuerpo de un tipo enum puede contener constantes enum. Una constante enum define una instancia del tipo enum. Un tipo enum no tiene más instancias que las definidas por sus constantes enum.

Jon Skeet
fuente
Me encantaría tomar su palabra, pero si pudiera proporcionar un enlace a alguna documentación oficial, sería mejor ...
Kip
El interruptor no es útil cuando hay mucha superposición entre diferentes casos. Además, RoundingMode es parte de java.math, por lo que no puedo agregarle un método.
Kip
2
Oh, ¿y dudas de Jon Skeet? No has estado por aquí mucho tiempo;)
Joel Coehoorn
enumeraciones en declaraciones switch? No sabía que era posible. Tendré que probarlo algún día.
luiscubal
Encapsular la lógica dentro de las enumeraciones utilizando métodos abstractos es el poder real de las enumeraciones. Hace que su código sea mucho más robusto; cuando agregue un nuevo valor de enumeración en el futuro, el compilador lo obligará a implementar la lógica relevante, no tiene que recordar agregar un caso a múltiples declaraciones de cambio.
Andrew Swan
13

Sí, es como si hubiera creado instancias singleton para cada valor en la enumeración:

RoundingMode de clase abstracta pública {
  RoundingMode final estático público HALF_UP = new RoundingMode ();
  RoundingMode final estático público HALF_EVEN = new RoundingMode ();

  Private RoundingMode () {
    // el ámbito privado evita cualquier subtipo fuera de esta clase
  }
}

Sin embargo , la enumconstrucción le brinda varios beneficios:

  • El toString () de cada instancia imprime el nombre dado en el código.
  • (Como se mencionó en otra publicación,) una variable del tipo enum se puede comparar con constantes usando la switch-caseestructura de control.
  • Todos los valores de la enumeración se pueden consultar utilizando el valuescampo que se 'genera' para cada tipo de enumeración
  • Aquí está la más importante de las comparaciones de identidad: los valores de enumeración sobreviven a la serialización sin clonación.

La serialización es una gran gotchya. Si tuviera que usar el código anterior en lugar de una enumeración, así es como se comportaría la igualdad de identidad:

RoundingMode original = RoundingMode.HALF_UP;
afirmar (RoundingMode.HALF_UP == original); // pasa

ByteArrayOutputStream baos = nuevo ByteArrayOutputStream ();
ObjectOutputStream oos = nuevo ObjectOutputStream (baos);
oos.writeObject (original);
oos.flush ();

ByteArrayInputStream bais = new ByteArrayInputStream (baos.toByteArray ());
ObjectInputStream ois = nuevo ObjectInputStream (bais);
RoundingMode deserializado = (RoundingMode) ois.readObject ();

afirmar (RoundingMode.HALF_UP == deserializado); // falla
afirmar (RoundingMode.HALF_EVEN == deserializado); // falla

Usted puede abordar esta cuestión sin enumeración, utilizando una técnica que implica writeReplacey readResolve, (ver http://java.sun.com/j2se/1.4.2/docs/api/java/io/Serializable.html ) ...

Supongo que el punto es que Java hace todo lo posible para permitirle usar las identidades de los valores de enumeración para probar la igualdad; es una práctica recomendada.

Dilum Ranatunga
fuente
1
Se corrigió el error de serialización. bugs.sun.com/bugdatabase/view_bug.do?bug_id=6277781
David I.
@DavidI. gracias por la actualización. Es un error muy perturbador, ¡y es bueno saberlo!
Dilum Ranatunga
1
@DilumRanatunga Pensé que esto me iba a afectar al principio, pero parece que funcionan bien después de pasarlos por una conexión RMI.
David I.
6

Aquí hay un código maligno que puede resultarle interesante. :RE

public enum YesNo {YES, NO}

public static void main(String... args) throws Exception {
    Field field = Unsafe.class.getDeclaredField("theUnsafe");
    field.setAccessible(true);
    Unsafe unsafe = (Unsafe) field.get(null);
    YesNo yesNo = (YesNo) unsafe.allocateInstance(YesNo.class);

    Field name = Enum.class.getDeclaredField("name");
    name.setAccessible(true);
    name.set(yesNo, "YES");

    Field ordinal = Enum.class.getDeclaredField("ordinal");
    ordinal.setAccessible(true);
    ordinal.set(yesNo, 0);

    System.out.println("yesNo " + yesNo);
    System.out.println("YesNo.YES.name().equals(yesNo.name()) "+YesNo.YES.name().equals(yesNo.name()));
    System.out.println("YesNo.YES.ordinal() == yesNo.ordinal() "+(YesNo.YES.ordinal() == yesNo.ordinal()));
    System.out.println("YesNo.YES.equals(yesNo) "+YesNo.YES.equals(yesNo));
    System.out.println("YesNo.YES == yesNo " + (YesNo.YES == yesNo));
}
Peter Lawrey
fuente
1
@Peter, ¿puedes incluir las importaciones de este código? Cound no encontrar Unsafe.class.
rumman0786
3

Las enumeraciones son un gran lugar para bloquear código polimórfico.

enum Rounding {
  ROUND_UP {
    public int round(double n) { ...; }
  },
  ROUND_DOWN {
    public int round(double n) { ...; }
  };

  public abstract int round(double n);
}

int foo(Rounding roundMethod) {
  return roundMethod.round(someCalculation());
}

int bar() {
  return foo(Rounding.ROUND_UP);
}
Paulmurray
fuente
1
Sí, pero no soy propietario de java.math.RoundingMode, por lo que no puedo hacer esto en mi caso.
Kip