Verwenden Sie == (oder! =), Um Java-Enums zu vergleichen

Die meisten neuen Java-Entwickler lernen schnell, dass sie Java-Strings im Allgemeinen mit String.equals (Object) vergleichen sollten, anstatt sie zu verwenden ==. Dies wird neuen Entwicklern wiederholt betont und verstärkt, da sie fast immer den String-Inhalt (die tatsächlichen Zeichen, die den String bilden) und nicht die Identität des Strings (seine Adresse im Speicher) vergleichen möchten. Ich behaupte, wir sollten den Begriff verstärken, der ==anstelle von Enum.equals (Object) verwendet werden kann. Ich begründe diese Behauptung im weiteren Verlauf dieses Beitrags.

Es gibt vier Gründe, aus denen ich glaube, dass die Verwendung ==zum Vergleichen von Java-Aufzählungen der Verwendung der "equals" -Methode fast immer vorzuziehen ist:

  1. Die ==on-Aufzählungen bieten den gleichen erwarteten Vergleich (Inhalt) wieequals
  2. Die ==Aufzählungen sind wohl besser lesbar (weniger ausführlich) alsequals
  3. Die ==Aufzählung ist null-sicherer alsequals
  4. Die Ein- ==Enums bieten eine (statische) Überprüfung zur Kompilierungszeit anstelle einer Laufzeitprüfung

Der zweite oben aufgeführte Grund ("wohl besser lesbar") ist offensichtlich Ansichtssache, aber dieser Teil über "weniger ausführlich" kann vereinbart werden. Der erste Grund, den ich ==beim Vergleichen von Aufzählungen im Allgemeinen bevorzuge , ist eine Folge der Beschreibung der Aufzählungen durch die Java-Sprachspezifikation. In Abschnitt 8.9 ("Aufzählungen") heißt es:

Es ist ein Fehler beim Kompilieren, wenn versucht wird, einen Aufzählungstyp explizit zu instanziieren. Die endgültige Klonmethode in Enum stellt sicher, dass Enum-Konstanten niemals geklont werden können, und die spezielle Behandlung durch den Serialisierungsmechanismus stellt sicher, dass niemals doppelte Instanzen als Ergebnis der Deserialisierung erstellt werden. Die reflektierende Instanziierung von Aufzählungstypen ist verboten. Zusammen stellen diese vier Dinge sicher, dass keine Instanzen eines Aufzählungstyps existieren, die über die durch die Aufzählungskonstanten definierten hinausgehen.

Da es nur eine Instanz jeder Aufzählungskonstante gibt, ist es zulässig, beim Vergleich zweier Objektreferenzen den Operator == anstelle der Methode equals zu verwenden, wenn bekannt ist, dass mindestens eine von ihnen auf eine Aufzählungskonstante verweist. (Die equals-Methode in Enum ist eine endgültige Methode, die lediglich super.equals für ihr Argument aufruft und das Ergebnis zurückgibt, wodurch ein Identitätsvergleich durchgeführt wird.)

Der Auszug aus der oben gezeigten Spezifikation impliziert und besagt dann explizit, dass es sicher ist, den ==Operator zum Vergleichen von zwei Aufzählungen zu verwenden, da es auf keinen Fall mehr als eine Instanz derselben Aufzählungskonstante geben kann.

Der vierte Vorteil ==gegenüber .equalsdem Vergleich von Aufzählungen hat mit der Sicherheit während der Kompilierung zu tun. Die Verwendung von ==Kräften erfordert eine strengere Überprüfung der Kompilierungszeit als die, .equalsda Object.equals (Object) vertraglich eine willkürliche Prüfung durchführen muss Object. Wenn Sie eine statisch typisierte Sprache wie Java verwenden, möchte ich die Vorteile dieser statischen Typisierung so gut wie möglich nutzen. Andernfalls würde ich eine dynamisch typisierte Sprache verwenden. Ich glaube, dass eines der wiederkehrenden Themen von Effective Java genau das ist: Bevorzugen Sie nach Möglichkeit die statische Typprüfung.

Angenommen, ich hatte eine benutzerdefinierte Aufzählung mit dem Namen Fruitund habe versucht, sie mit der Klasse java.awt.Color zu vergleichen. Durch die Verwendung des ==Operators kann ich einen Fehler beim Kompilieren (einschließlich einer Vorankündigung in meiner bevorzugten Java-IDE) des Problems erhalten. Hier ist eine Codeliste, die versucht, eine benutzerdefinierte Aufzählung mit einer JDK-Klasse mithilfe des ==Operators zu vergleichen:

/** * Indicate if provided Color is a watermelon. * * This method's implementation is commented out to avoid a compiler error * that legitimately disallows == to compare two objects that are not and * cannot be the same thing ever. * * @param candidateColor Color that will never be a watermelon. * @return Should never be true. */ public boolean isColorWatermelon(java.awt.Color candidateColor) { // This comparison of Fruit to Color will lead to compiler error: // error: incomparable types: Fruit and Color return Fruit.WATERMELON == candidateColor; } 

Der Compilerfehler wird im nächsten Screenshot angezeigt.

Obwohl ich kein Fan von Fehlern bin, bevorzuge ich, dass sie zur Kompilierungszeit statisch abgefangen werden, anstatt von der Laufzeitabdeckung abhängig zu sein. Hätte ich die equalsMethode für diesen Vergleich verwendet, wäre der Code gut kompiliert worden, aber die Methode würde immer falsefalse zurückgeben, da eine dustin.examples.FruitAufzählung auf keinen Fall einer java.awt.ColorKlasse entspricht. Ich empfehle es nicht, aber hier ist die Vergleichsmethode mit .equals:

/** * Indicate whether provided Color is a Raspberry. This is utter nonsense * because a Color can never be equal to a Fruit, but the compiler allows this * check and only a runtime determination can indicate that they are not * equal even though they can never be equal. This is how NOT to do things. * * @param candidateColor Color that will never be a raspberry. * @return {@code false}. Always. */ public boolean isColorRaspberry(java.awt.Color candidateColor) { // // DON'T DO THIS: Waste of effort and misleading code!!!!!!!! // return Fruit.RASPBERRY.equals(candidateColor); } 

Das "Schöne" an dem oben genannten ist das Fehlen von Fehlern bei der Kompilierung. Es kompiliert wunderschön. Leider wird dies mit einem möglicherweise hohen Preis bezahlt.

Der letzte Vorteil, den ich aufgeführt habe, ==anstatt Enum.equalsAufzählungen zu vergleichen, ist die Vermeidung der gefürchteten NullPointerException. Wie ich in Effective Java NullPointerException Handling angegeben habe, vermeide ich im Allgemeinen gerne unerwartete NullPointerExceptions. Es gibt eine begrenzte Anzahl von Situationen, in denen ich wirklich möchte, dass die Existenz einer Null als Ausnahmefall behandelt wird, aber oft bevorzuge ich eine elegantere Berichterstattung über ein Problem. Ein Vorteil des Vergleichs von Aufzählungen mit ==besteht darin, dass eine Null mit einer Nicht-Null-Aufzählung verglichen werden kann, ohne auf eine NullPointerException(NPE) zu stoßen . Das Ergebnis dieses Vergleichs ist offensichtlich false.

Eine Möglichkeit, die NPE bei der Verwendung zu vermeiden, .equals(Object)besteht darin, die equalsMethode für eine Aufzählungskonstante oder eine bekannte Nicht-Null-Aufzählung aufzurufen und dann die potenzielle Aufzählung mit fragwürdigem Charakter (möglicherweise Null) als Parameter an die equalsMethode zu übergeben. Dies wird in Java oft jahrelang mit Strings durchgeführt, um die NPE zu vermeiden. Für den ==Bediener spielt die Vergleichsreihenfolge jedoch keine Rolle. Ich mag es.

Ich habe meine Argumente vorgebracht und gehe nun zu einigen Codebeispielen über. Die nächste Auflistung ist eine Realisierung der zuvor erwähnten hypothetischen Fruchtaufzählung.

Fruit.java

package dustin.examples; public enum Fruit { APPLE, BANANA, BLACKBERRY, BLUEBERRY, CHERRY, GRAPE, KIWI, MANGO, ORANGE, RASPBERRY, STRAWBERRY, TOMATO, WATERMELON } 

Die nächste Codeliste ist eine einfache Java-Klasse, die Methoden zum Erkennen, ob eine bestimmte Aufzählung oder ein bestimmtes Objekt eine bestimmte Frucht ist, bereitstellt. Normalerweise würde ich solche Schecks in die Aufzählung selbst einfügen, aber sie funktionieren hier in einer separaten Klasse besser für meine illustrativen und demonstrativen Zwecke. Zu dieser Klasse gehören die beiden Methoden gezeigt früher für den Vergleich Fruitzu Colormit beiden ==und equals. Bei der Methode ==zum Vergleichen einer Aufzählung mit einer Klasse musste dieser Teil natürlich auskommentiert werden, um ordnungsgemäß kompiliert zu werden.

EnumComparisonMain.java

package dustin.examples; public class EnumComparisonMain { /** * Indicate whether provided fruit is a watermelon ({@code true} or not * ({@code false}). * * @param candidateFruit Fruit that may or may not be a watermelon; null is * perfectly acceptable (bring it on!). * @return {@code true} if provided fruit is watermelon; {@code false} if * provided fruit is NOT a watermelon. */ public boolean isFruitWatermelon(Fruit candidateFruit) { return candidateFruit == Fruit.WATERMELON; } /** * Indicate whether provided object is a Fruit.WATERMELON ({@code true}) or * not ({@code false}). * * @param candidateObject Object that may or may not be a watermelon and may * not even be a Fruit! * @return {@code true} if provided object is a Fruit.WATERMELON; * {@code false} if provided object is not Fruit.WATERMELON. */ public boolean isObjectWatermelon(Object candidateObject) { return candidateObject == Fruit.WATERMELON; } /** * Indicate if provided Color is a watermelon. * * This method's implementation is commented out to avoid a compiler error * that legitimately disallows == to compare two objects that are not and * cannot be the same thing ever. * * @param candidateColor Color that will never be a watermelon. * @return Should never be true. */ public boolean isColorWatermelon(java.awt.Color candidateColor) { // Had to comment out comparison of Fruit to Color to avoid compiler error: // error: incomparable types: Fruit and Color return /*Fruit.WATERMELON == candidateColor*/ false; } /** * Indicate whether provided fruit is a strawberry ({@code true}) or not * ({@code false}). * * @param candidateFruit Fruit that may or may not be a strawberry; null is * perfectly acceptable (bring it on!). * @return {@code true} if provided fruit is strawberry; {@code false} if * provided fruit is NOT strawberry. */ public boolean isFruitStrawberry(Fruit candidateFruit) { return Fruit.STRAWBERRY == candidateFruit; } /** * Indicate whether provided fruit is a raspberry ({@code true}) or not * ({@code false}). * * @param candidateFruit Fruit that may or may not be a raspberry; null is * completely and entirely unacceptable; please don't pass null, please, * please, please. * @return {@code true} if provided fruit is raspberry; {@code false} if * provided fruit is NOT raspberry. */ public boolean isFruitRaspberry(Fruit candidateFruit) { return candidateFruit.equals(Fruit.RASPBERRY); } /** * Indicate whether provided Object is a Fruit.RASPBERRY ({@code true}) or * not ({@code false}). * * @param candidateObject Object that may or may not be a Raspberry and may * or may not even be a Fruit! * @return {@code true} if provided Object is a Fruit.RASPBERRY; {@code false} * if it is not a Fruit or not a raspberry. */ public boolean isObjectRaspberry(Object candidateObject) { return candidateObject.equals(Fruit.RASPBERRY); } /** * Indicate whether provided Color is a Raspberry. This is utter nonsense * because a Color can never be equal to a Fruit, but the compiler allows this * check and only a runtime determination can indicate that they are not * equal even though they can never be equal. This is how NOT to do things. * * @param candidateColor Color that will never be a raspberry. * @return {@code false}. Always. */ public boolean isColorRaspberry(java.awt.Color candidateColor) { // // DON'T DO THIS: Waste of effort and misleading code!!!!!!!! // return Fruit.RASPBERRY.equals(candidateColor); } /** * Indicate whether provided fruit is a grape ({@code true}) or not * ({@code false}). * * @param candidateFruit Fruit that may or may not be a grape; null is * perfectly acceptable (bring it on!). * @return {@code true} if provided fruit is a grape; {@code false} if * provided fruit is NOT a grape. */ public boolean isFruitGrape(Fruit candidateFruit) { return Fruit.GRAPE.equals(candidateFruit); } } 

Ich beschloss, mich der Demonstration der in den oben genannten Methoden erfassten Ideen durch Unit-Tests zu nähern. Insbesondere benutze ich Groovys GroovyTestCase. Diese Klasse für die Verwendung von Groovy-basierten Komponententests befindet sich in der nächsten Codeliste.

EnumComparisonTest.groovy