Posso usar == em enums em Java?

111

Posso usar ==enums em Java ou preciso usar .equals()? Nos meus testes, ==sempre funciona, mas não tenho certeza se tenho certeza disso. Em particular, não existe nenhum .clone()método em um enum, então não sei se é possível obter um enum para o qual .equals()retornaria um valor diferente de ==.

Por exemplo, isso está OK:

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

Ou preciso escrever desta forma:

public int round(RoundingMode roundingMode) {
  if(roundingMode.equals(RoundingMode.HALF_UP)) {
    //do something
  } else if (roundingMode.equals(RoundingMode.HALF_EVEN)) {
    //do something
  }
  //etc
}
Kip
fonte
4
possível duplicata de Comparing Java enum members: == ou equals ()?
Assylias
@assylias esta pergunta veio primeiro. Talvez sinalize para ♦ atenção, já que não tenho certeza se os dois devem ser mesclados.
Matt Ball,
@MattBall Acho que a resposta à sua pergunta que cita o JLS é a melhor resposta, por isso optei por encerrar este.
Assylias

Respostas:

149

Apenas meus 2 centavos: Aqui está o código para Enum.java, conforme publicado pela Sun, e parte do 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
fonte
4
Obrigado! Eu acho que se eu tivesse apenas pensado em entrar em .equals () com o compilador, eu teria visto isso ...
Kip em
77

Sim, == está bem - é garantido que haja apenas uma única referência para cada valor.

No entanto, há uma maneira melhor de escrever seu método redondo:

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

Uma maneira ainda melhor de fazer isso é colocar a funcionalidade dentro do próprio enum, para que você possa apenas chamar roundingMode.round(someValue). Isso vai ao cerne dos enums Java - eles são enums orientados a objetos , ao contrário dos "valores nomeados" encontrados em outros lugares.

EDIT: A especificação não é muito clara, mas a seção 8.9 afirma:

O corpo de um tipo de enum pode conter constantes de enum. Uma constante enum define uma instância do tipo enum. Um tipo de enum não tem instâncias diferentes daquelas definidas por suas constantes de enum.

Jon Skeet
fonte
Adoraria acreditar na sua palavra, mas se você pudesse fornecer um link para alguma documentação oficial seria melhor ...
Kip
switch não é útil quando há muita sobreposição entre casos diferentes. Além disso, RoundingMode faz parte de java.math, então não posso adicionar um método a ele.
Kip de
2
Oh - e você duvida de Jon Skeet? Você não está por aqui há muito tempo;)
Joel Coehoorn
enums em declarações switch? Não sabia que isso era possível. Vou ter que experimentar um dia.
luiscubal de
Encapsular a lógica em enums usando métodos abstratos é o verdadeiro poder dos enums. Isso torna seu código muito mais robusto; quando você adiciona um novo valor enum no futuro, o compilador o força a implementar a lógica relevante, você não precisa se lembrar de adicionar um caso a várias instruções switch.
Andrew Swan
13

Sim, é como se você tivesse criado instâncias singleton para cada valor no enum:

public abstract class RoundingMode {
  público estático final RoundingMode HALF_UP = new RoundingMode ();
  público estático final RoundingMode HALF_EVEN = new RoundingMode ();

  private RoundingMode () {
    // escopo privado impede qualquer subtipo fora desta classe
  }
}

No entanto , a enumconstrução oferece vários benefícios:

  • O toString () de cada instância imprime o nome fornecido no código.
  • (Como mencionado em outra postagem), uma variável do tipo enum pode ser comparada com constantes usando a switch-caseestrutura de controle.
  • Todos os valores na enumeração podem ser consultados usando o valuescampo que é 'gerado' para cada tipo de enum
  • Aqui está a grande comparação de identidade: valores enum sobrevivem à serialização sem clonagem.

A serialização é uma grande pegadinha. Se eu fosse usar o código acima em vez de um enum, eis como a igualdade de identidade se comportaria:

RoundingMode original = RoundingMode.HALF_UP;
assert (RoundingMode.HALF_UP == original); // passa

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

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

assert (RoundingMode.HALF_UP == desserializado); // falha
assert (RoundingMode.HALF_EVEN == desserializado); // falha

Você pode resolver esse problema sem enum, usando uma técnica que envolve writeReplacee readResolve, (consulte http://java.sun.com/j2se/1.4.2/docs/api/java/io/Serializable.html ) ...

Eu acho que o ponto é - Java sai de seu caminho para permitir que você use identidades de valores enum para testar a igualdade; é uma prática incentivada.

Dilum Ranatunga
fonte
1
bug de serialização foi corrigido. bugs.sun.com/bugdatabase/view_bug.do?bug_id=6277781
David I.
@DavidI. obrigado pela atualização. Esse é um bug muito perturbador e bom saber!
Dilum Ranatunga
1
@DilumRanatunga Achei que isso fosse me afetar no início, mas eles parecem estar funcionando bem depois de transmiti-los por uma conexão RMI.
David I.
6

Aqui está um código maligno que você pode achar interessante. : D

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
fonte
1
@Peter você pode incluir as importações deste código? Cound não encontra Unsafe.class.
rumman0786
3

Enums são um ótimo 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
fonte
1
Sim, mas não possuo java.math.RoundingMode, então não posso fazer isso no meu caso.
Kip
0

== está geralmente bem, e há vantagens para == e .equals(). Eu pessoalmente prefiro sempre usar .equals()ao comparar objetos, incluindo enums. Veja também esta discussão:

Comparando membros enum Java: == ou equals ()?

Tobias
fonte