Ist es in Ordnung, == für Aufzählungen in Java zu verwenden?

111

Ist es in Ordnung, ==Enums in Java zu verwenden, oder muss ich es verwenden .equals()? Funktioniert bei meinen Tests ==immer, aber ich bin mir nicht sicher, ob mir das garantiert ist. Insbesondere gibt es keine .clone()Methode für eine Aufzählung, daher weiß ich nicht, ob es möglich ist, eine Aufzählung zu erhalten, für die .equals()ein anderer Wert als zurückgegeben wird ==.

Ist das zum Beispiel in Ordnung:

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

Oder muss ich es so schreiben:

public int round(RoundingMode roundingMode) {
  if(roundingMode.equals(RoundingMode.HALF_UP)) {
    //do something
  } else if (roundingMode.equals(RoundingMode.HALF_EVEN)) {
    //do something
  }
  //etc
}
Pennen
quelle
@assylias diese Frage kam zuerst. Vielleicht Flagge für ♦ Aufmerksamkeit, da ich nicht wirklich sicher bin, ob die beiden zusammengeführt werden sollen.
Matt Ball
@MattBall Ich denke, die Antwort auf Ihre Frage, die das JLS zitiert, ist die beste Antwort, weshalb ich mich entschieden habe, diese zu schließen.
Assylias

Antworten:

149

Nur meine 2 Cent: Hier ist der Code für Enum.java, wie er von Sun veröffentlicht wurde und Teil des 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
quelle
4
Vielen Dank! Ich denke, wenn ich nur daran gedacht hätte, mit dem Compiler in .equals () einzusteigen, hätte ich das gesehen ...
Kip
77

Ja, == ist in Ordnung - es gibt garantiert nur eine einzige Referenz für jeden Wert.

Es gibt jedoch eine bessere Möglichkeit, Ihre runde Methode zu schreiben:

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

Eine noch bessere Möglichkeit besteht darin, die Funktionalität in die Enumeration selbst zu integrieren, sodass Sie einfach anrufen können roundingMode.round(someValue). Dies bringt Java-Enums auf den Punkt - sie sind objektorientierte Enums, im Gegensatz zu den "benannten Werten", die an anderer Stelle zu finden sind.

EDIT: Die Spezifikation ist nicht sehr klar, aber Abschnitt 8.9 besagt:

Der Körper eines Aufzählungstyps kann Aufzählungskonstanten enthalten. Eine Aufzählungskonstante definiert eine Instanz des Aufzählungstyps. Ein Aufzählungstyp hat keine anderen Instanzen als die durch seine Aufzählungskonstanten definierten.

Jon Skeet
quelle
Ich würde gerne Ihr Wort dafür nehmen, aber wenn Sie einen Link zu einer offiziellen Dokumentation bereitstellen könnten, wäre das besser ...
Kip
Schalter ist nicht nützlich, wenn es viele Überlappungen zwischen verschiedenen Fällen gibt. Außerdem ist RoundingMode Teil von java.math, daher kann ich ihm keine Methode hinzufügen.
Kip
2
Oh ... und du zweifelst an Jon Skeet? Du bist noch nicht lange hier;)
Joel Coehoorn
Aufzählungen in switch-Anweisungen? Wusste nicht, dass das möglich ist. Ich muss es eines Tages ausprobieren.
luiscubal
Das Einkapseln von Logik in Aufzählungen mit abstrakten Methoden ist die wahre Kraft von Aufzählungen. Es macht Ihren Code viel robuster; Wenn Sie in Zukunft einen neuen Enum-Wert hinzufügen, werden Sie vom Compiler gezwungen, die entsprechende Logik zu implementieren. Sie müssen nicht daran denken, mehreren switch-Anweisungen einen Fall hinzuzufügen.
Andrew Swan
13

Ja, es ist, als hätten Sie Singleton-Instanzen für jeden Wert in der Aufzählung erstellt:

öffentliche abstrakte Klasse RoundingMode {
  public static final RoundingMode HALF_UP = neuer RoundingMode ();
  public static final RoundingMode HALF_EVEN = neuer RoundingMode ();

  privater RoundingMode () {
    // privater Bereich verhindert Subtypen außerhalb dieser Klasse
  }}
}}

Allerdings , das enumgibt Konstrukt Ihnen verschiedene Vorteile:

  • ToString () jeder Instanz gibt den im Code angegebenen Namen aus.
  • (Wie in einem anderen Beitrag erwähnt) Eine Variable des Aufzählungstyps kann mithilfe der switch-caseKontrollstruktur mit Konstanten verglichen werden.
  • Alle Werte in der Aufzählung können mithilfe des valuesFelds abgefragt werden, das für jeden Aufzählungstyp "generiert" wird
  • Hier ist der große Punkt für Identitätsvergleiche: Enum-Werte überleben die Serialisierung ohne Klonen.

Die Serialisierung ist eine große Gotchya. Wenn ich den obigen Code anstelle einer Aufzählung verwenden würde, würde sich die Identitätsgleichheit folgendermaßen verhalten:

RoundingMode original = RoundingMode.HALF_UP;
assert (RoundingMode.HALF_UP == original); // geht vorbei

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

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

assert (RoundingMode.HALF_UP == deserialisiert); // schlägt fehl
assert (RoundingMode.HALF_EVEN == deserialisiert); // schlägt fehl

Sie können ohne Enum dieses Problem beheben, eine Technik , die beinhaltet writeReplaceund readResolve, (siehe http://java.sun.com/j2se/1.4.2/docs/api/java/io/Serializable.html ) ...

Ich denke, der Punkt ist: Java gibt sich alle Mühe, die Identität von Enum-Werten zum Testen der Gleichheit zu verwenden. Es ist eine ermutigte Praxis.

Dilum Ranatunga
quelle
1
Serialisierungsfehler wurde behoben. bugs.sun.com/bugdatabase/view_bug.do?bug_id=6277781
David I.
@ DavidI. Danke für das Update. Das ist ein sehr beunruhigender Fehler und gut zu wissen!
Dilum Ranatunga
1
@ DilumRanatunga Ich dachte, das würde mich zuerst betreffen, aber sie scheinen in Ordnung zu sein, nachdem sie über eine RMI-Verbindung übertragen wurden.
David I.
6

Hier ist ein böser Code, den Sie vielleicht interessant finden. : 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
quelle
1
@Peter können Sie bitte die Importe dieses Codes einschließen? Cound nicht finden Unsafe.class.
rumman0786
3

Aufzählungen sind ein großartiger Ort, um polymorphen Code zu stören.

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
quelle
1
Ja, aber ich besitze java.math.RoundingMode nicht, daher kann ich dies in meinem Fall nicht tun.
Kip
0

== ist im Allgemeinen in Ordnung und es gibt Vorteile für == und .equals(). Ich persönlich bevorzuge es immer, .equals()wenn ich Objekte vergleiche, einschließlich enums. Siehe auch diese Diskussion:

Java-Enum-Mitglieder vergleichen: == oder equals ()?

Tobias
quelle