Czy można używać == na wyliczeniach w Javie?

111

Czy ==mogę używać na wyliczeniach w Javie, czy muszę używać .equals()? W moich testach ==zawsze działa, ale nie jestem pewien, czy to gwarantuje. W szczególności nie ma .clone()metody na wyliczeniu, więc nie wiem, czy można uzyskać wyliczenie, dla którego zwróciłoby .equals()inną wartość niż ==.

Na przykład, czy to jest w porządku:

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

Czy też muszę napisać to w ten sposób:

public int round(RoundingMode roundingMode) {
  if(roundingMode.equals(RoundingMode.HALF_UP)) {
    //do something
  } else if (roundingMode.equals(RoundingMode.HALF_EVEN)) {
    //do something
  }
  //etc
}
Wyrko
źródło
@assylias to pytanie było pierwsze. Być może flaga dla ♦ uwagi, ponieważ nie jestem pewien, czy te dwa elementy powinny być połączone.
Matt Ball,
@MattBall Myślę, że odpowiedź na twoje pytanie, która cytuje JLS, jest najlepszą odpowiedzią, dlatego zdecydowałem się zamknąć to.
assylias

Odpowiedzi:

149

Tylko moje 2 centy: Oto kod Enum.java opublikowany przez Sun i część 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
źródło
4
Dzięki! Wydaje mi się, że gdybym pomyślał o wejściu do .equals () za pomocą kompilatora, zobaczyłbym to ...
Kip,
77

Tak, == jest w porządku - istnieje gwarancja, że ​​dla każdej wartości będzie tylko jedno odwołanie.

Istnieje jednak lepszy sposób na zapisanie metody rundy:

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

Jeszcze lepszym sposobem na zrobienie tego jest umieszczenie funkcji w samym wyliczeniu, więc możesz po prostu zadzwonić roundingMode.round(someValue). Dochodzi do sedna wyliczeń Java - są to wyliczenia obiektowe , w przeciwieństwie do „nazwanych wartości”, które można znaleźć gdzie indziej.

EDYCJA: specyfikacja nie jest zbyt jasna, ale sekcja 8.9 mówi:

Treść typu wyliczenia może zawierać stałe wyliczenia. Stała wyliczeniowa definiuje wystąpienie typu wyliczenia. Typ wyliczenia nie ma innych wystąpień niż te zdefiniowane przez jego stałe wyliczeniowe.

Jon Skeet
źródło
Chciałbym Ci wierzyć na słowo, ale gdybyś mógł podać link do jakiejś oficjalnej dokumentacji, która byłaby lepsza ...
Kip,
przełącznik nie jest przydatny, gdy różne przypadki nakładają się na siebie. Ponadto RoundingMode jest częścią java.math, więc nie mogę dodać do niego metody.
Kip
2
Och ... i wątpisz w Jona Skeeta? Nie byłeś tu zbyt długo;)
Joel Coehoorn
wyliczenia w instrukcjach przełączania? Nie wiedziałem, że to możliwe. Będę musiał kiedyś to wypróbować.
luiscubal
Hermetyzacja logiki w wyliczeniach przy użyciu metod abstrakcyjnych to prawdziwa moc wyliczeń. Dzięki temu Twój kod jest znacznie bardziej niezawodny; Kiedy dodasz nową wartość wyliczenia w przyszłości, kompilator zmusi Cię do zaimplementowania odpowiedniej logiki, nie musisz pamiętać o dodawaniu przypadku do wielu instrukcji przełącznika.
Andrew Swan
13

Tak, to tak, jakbyś utworzył pojedyncze wystąpienia dla każdej wartości w wyliczeniu:

publiczna klasa abstrakcyjna RoundingMode {
  public statyczny końcowy RoundingMode HALF_UP = new RoundingMode ();
  public statyczny końcowy RoundingMode HALF_EVEN = new RoundingMode ();

  private RoundingMode () {
    // zakres prywatny zapobiega wszelkim podtypom poza tą klasą
  }
}

Jednak The enumkonstrukcja daje wiele korzyści:

  • Metoda toString () każdej instancji wyświetla nazwę podaną w kodzie.
  • (Jak wspomniano w innym poście) zmienną typu wyliczenia można porównać ze stałymi za switch-casepomocą struktury kontrolnej.
  • Wszystkie wartości w wyliczeniu można przeszukiwać za pomocą valuespola, które jest „generowane” dla każdego typu wyliczenia
  • Oto największe porównania tożsamości: wartości wyliczenia przetrwają serializację bez klonowania.

Serializacja to duży problem. Gdybym miał użyć powyższego kodu zamiast wyliczenia, oto jak zachowałaby się równość tożsamości:

RoundingMode original = RoundingMode.HALF_UP;
assert (RoundingMode.HALF_UP == oryginał); // Karnety

ByteArrayOutputStream baos = new ByteArrayOutputStream ();
ObjectOutputStream oos = nowy ObjectOutputStream (baos);
oos.writeObject (oryginał);
oos.flush ();

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

assert (RoundingMode.HALF_UP == deserialized); // nie udaje się
assert (RoundingMode.HALF_EVEN == deserialized); // nie udaje się

Państwo może rozwiązać ten problem bez wyliczenia, stosując technikę, która obejmuje writeReplacei readResolve, (patrz http://java.sun.com/j2se/1.4.2/docs/api/java/io/Serializable.html ) ...

Chyba chodzi o to, że Java robi wszystko, co w jej mocy, aby umożliwić używanie tożsamości wartości wyliczeniowych do testowania równości; jest to zalecana praktyka.

Dilum Ranatunga
źródło
1
naprawiono błąd serializacji. bugs.sun.com/bugdatabase/view_bug.do?bug_id=6277781
David I.,
@DavidI. Dziękuję za aktualizację. To bardzo niepokojący błąd i dobrze wiedzieć!
Dilum Ranatunga
1
@DilumRanatunga Na początku myślałem, że to wpłynie na mnie, ale wydaje się, że działają dobrze po przekazaniu ich przez połączenie RMI.
David I.
6

Oto zły kod, który może Cię zainteresować. :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
źródło
1
@Peter czy możesz dołączyć import tego kodu? Nie znaleziono Unsafe.class.
rumman0786
3

Wyliczenia to świetne miejsce do zakleszczenia kodu polimorficznego.

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
źródło
1
Tak, ale nie posiadam java.math.RoundingMode, więc nie mogę tego zrobić w moim przypadku.
Kip