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
}


@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;
    }


}

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.


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.


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

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

1
Ja, aber ich besitze java.math.RoundingMode nicht, daher kann ich dies in meinem Fall nicht tun.
Kip


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.