Verwendung typsicherer Aufzählungen in Java

Java-Code, der traditionelle Aufzählungstypen verwendet, ist problematisch. Java 5 gab uns eine bessere Alternative in Form von typsicheren Aufzählungen. In diesem Artikel stelle ich Ihnen aufgezählte Typen und typsichere Aufzählungen vor, zeige Ihnen, wie Sie eine typsichere Aufzählung deklarieren und in einer switch-Anweisung verwenden, und erläutere das Anpassen einer typsicheren Aufzählung durch Hinzufügen von Daten und Verhalten. Ich schließe den Artikel ab, indem ich die Klasse erkunde .java.lang.Enum

download Code herunterladen Laden Sie den Quellcode herunter, um Beispiele in diesem Java 101-Tutorial zu erhalten. Erstellt von Jeff Friesen für JavaWorld /.

Von aufgezählten Typen bis zu typsicheren Aufzählungen

Ein Aufzählungstyp gibt eine Reihe zusammengehöriger Konstanten als Werte an. Beispiele sind eine Woche von Tagen, die Standard-Kompassrichtungen Nord / Süd / Ost / West, die Münzwerte einer Währung und die Token-Typen eines lexikalischen Analysators.

Aufzählungstypen wurden traditionell als Folgen von Ganzzahlkonstanten implementiert, was durch den folgenden Satz von Richtungskonstanten demonstriert wird:

statisch final int DIR_NORTH = 0; statisch final int DIR_WEST = 1; statisch final int DIR_EAST = 2; statisch final int DIR_SOUTH = 3;

Bei diesem Ansatz gibt es mehrere Probleme:

  • Mangelnde Typensicherheit: Da eine aufgezählte Typkonstante nur eine Ganzzahl ist, kann jede Ganzzahl angegeben werden, wenn die Konstante erforderlich ist. Darüber hinaus können Additionen, Subtraktionen und andere mathematische Operationen an diesen Konstanten durchgeführt werden. zum Beispiel (DIR_NORTH + DIR_EAST) / DIR_SOUTH), was bedeutungslos ist.
  • Namespace nicht vorhanden: Den Konstanten eines Aufzählungstyps muss eine (hoffentlich) eindeutige Kennung (z. B. DIR_) vorangestellt werden , um Kollisionen mit den Konstanten eines anderen Aufzählungstyps zu verhindern.
  • Brüchigkeit: Da aufgezählte Typkonstanten in Klassendateien kompiliert werden, in denen ihre Literalwerte gespeichert sind (in konstanten Pools), müssen diese Klassendateien und die von ihnen abhängigen Anwendungsklassendateien neu erstellt werden, um den Wert einer Konstante zu ändern. Andernfalls tritt zur Laufzeit ein undefiniertes Verhalten auf.
  • Mangel an Informationen: Wenn eine Konstante gedruckt wird, wird ihr ganzzahliger Wert ausgegeben. Diese Ausgabe sagt nichts darüber aus, was der ganzzahlige Wert darstellt. Es wird nicht einmal der Aufzählungstyp identifiziert, zu dem die Konstante gehört.

Durch die Verwendung von java.lang.StringKonstanten können Sie die Probleme „mangelnde Typensicherheit“ und „mangelnde Information“ vermeiden . Zum Beispiel könnten Sie angeben static final String DIR_NORTH = "NORTH";. Obwohl der konstante Wert aussagekräftiger ist, Stringleiden Konstanten auf der Basis von "Namespace nicht vorhanden" und Sprödigkeitsproblemen. Im Gegensatz zu Ganzzahlvergleichen können Sie Zeichenfolgenwerte auch nicht mit den Operatoren ==und !=vergleichen (die nur Referenzen vergleichen).

Diese Probleme veranlassten Entwickler, eine klassenbasierte Alternative namens Typesafe Enum zu erfinden . Dieses Muster wurde ausführlich beschrieben und kritisiert. Joshua Bloch führte das Muster in Punkt 21 seines Handbuchs zur effektiven Java-Programmiersprache (Addison-Wesley, 2001) ein und stellte fest, dass es einige Probleme gibt; Das heißt, es ist umständlich, typsichere Enum-Konstanten in Mengen zu aggregieren, und Enumerationskonstanten können nicht in switchAnweisungen verwendet werden.

Betrachten Sie das folgende Beispiel für das typsichere Aufzählungsmuster. Die SuitKlasse zeigt, wie Sie die klassenbasierte Alternative verwenden können, um einen Aufzählungstyp einzuführen, der die vier Kartenanzüge (Keulen, Diamanten, Herzen und Pik) beschreibt:

public final class Suit // Sollte nicht in der Lage sein, Suit zu unterklassifizieren. {public static final Suit CLUBS = neuer Anzug (); public static final Suit DIAMONDS = neuer Anzug (); public static final Suit HEARTS = neuer Anzug (); public static final Suit SPADES = neuer Anzug (); private Suit () {} // Sollte keine zusätzlichen Konstanten einführen können. }}

Um diese Klasse zu verwenden, führen Sie eine SuitVariable ein und weisen sie Suitwie folgt einer der Konstanten zu:

Anzug Anzug = Anzug.DIAMONDS;

Vielleicht möchten Sie dann suiteine switchAussage wie diese abfragen :

Schalter (Anzug) {Fall Anzug.CLUBS: System.out.println ("Clubs"); Unterbrechung; case Suit.DIAMONDS: System.out.println ("Diamanten"); Unterbrechung; case Suit.HEARTS: System.out.println ("Herzen"); Unterbrechung; case Suit.SPADES: System.out.println ("Pik"); }}

Wenn der Java-Compiler jedoch auf Suit.CLUBSeinen Fehler stößt , wird ein Fehler gemeldet, der besagt, dass ein konstanter Ausdruck erforderlich ist. Sie können versuchen, das Problem wie folgt zu beheben:

Schalter (Anzug) {Fall CLUBS: System.out.println ("Clubs"); Unterbrechung; case DIAMONDS: System.out.println ("Diamanten"); Unterbrechung; case HEARTS: System.out.println ("Herzen"); Unterbrechung; case SPADES: System.out.println ("Pik"); }}

Wenn der Compiler jedoch auf etwas stößt CLUBS, wird ein Fehler gemeldet, der besagt, dass das Symbol nicht gefunden werden konnte. Und selbst wenn Sie platziert Suitin einem Paket, importiert das Paket, und statisch diese Konstanten importiert, würde der Compiler beschweren sich, dass es nicht konvertieren kann , Suitum intbei der Begegnung suitin switch(suit). In Bezug auf jede casewürde der Compiler auch melden, dass ein konstanter Ausdruck erforderlich ist.

Java unterstützt das Typesafe Enum-Muster mit switchAnweisungen nicht. Es wurde jedoch die typsichere Enum-Sprachfunktion eingeführt , um die Vorteile des Musters bei der Lösung seiner Probleme zusammenzufassen. Diese Funktion wird jedoch unterstützt switch.

Deklarieren einer typsicheren Aufzählung und Verwenden in einer switch-Anweisung

Eine einfache typsichere Enumerationsdeklaration in Java-Code sieht aus wie ihre Gegenstücke in den Sprachen C, C ++ und C #:

Aufzählungsrichtung {NORD, WEST, OST, SÜD}

Diese Deklaration verwendet das Schlüsselwort enum, um Directioneine typsichere Aufzählung (eine spezielle Art von Klasse) einzuführen, in der beliebige Methoden hinzugefügt und beliebige Schnittstellen implementiert werden können. Die NORTH, WEST, EASTund SOUTHENUM - Konstanten werden als konstant spezifische Klasse Stellen durchgeführt , die anonyme Klassen Erweiterung der umschließenden definieren DirectionKlasse.

Directionund andere typsichere Aufzählungen erweitern  und verschiedene Methoden erben, einschließlich , und aus dieser Klasse. Wir werden später in diesem Artikel untersuchen.Enum values()toString()compareTo()Enum

Listing 1 deklariert die oben genannte Aufzählung und verwendet sie in einer switchAnweisung. Es wird auch gezeigt, wie zwei Enum-Konstanten verglichen werden, um festzustellen, welche Konstante vor der anderen Konstante steht.

Listing 1: TEDemo.java(Version 1)

öffentliche Klasse TEDemo {enum Richtung {NORD, WEST, OST, SÜD} public static void main (String [] args) {für (int i = 0; i <Direction.values ​​(). length; i ++) {Richtung d = Richtung .values ​​() [i]; System.out.println (d); Schalter (d) {case NORTH: System.out.println ("Nach Norden bewegen"); Unterbrechung; case WEST: System.out.println ("Nach Westen bewegen"); Unterbrechung; Fall OSTEN: System.out.println ("Nach Osten bewegen"); Unterbrechung; case SOUTH: System.out.println ("Nach Süden bewegen"); Unterbrechung; default: assert false: "unbekannte Richtung"; }} System.out.println (Direction.NORTH.compareTo (Direction.SOUTH)); }}

Listing 1 deklariert die Directiontypsichere Aufzählung und iteriert über die konstanten Elemente, die values()zurückgegeben werden. Für jeden Wert switchwählt die Anweisung (erweitert, um typsichere Aufzählungen zu unterstützen) casediejenige aus, die dem Wert von entspricht,  d und gibt eine entsprechende Nachricht aus. (Sie stellen einer Aufzählungskonstante, z. B. NORTHihrem Aufzählungstyp, kein Präfix voran .) Schließlich wird in Listing 1 ausgewertet, Direction.NORTH.compareTo(Direction.SOUTH)um festzustellen, ob sie NORTHvorher kommt SOUTH.

Kompilieren Sie den Quellcode wie folgt:

javac TEDemo.java

Führen Sie die kompilierte Anwendung wie folgt aus:

Java TEDemo

Sie sollten die folgende Ausgabe beachten:

NORD Bewegen Sie sich nach Norden WESTEN Bewegen Sie sich nach Westen OSTEN Bewegen Sie sich nach Osten SÜD Bewegen Sie sich nach Süden -3

Die Ausgabe zeigt, dass die geerbte toString()Methode den Namen der Enum-Konstante zurückgibt, und dies NORTHkommt SOUTHbei einem Vergleich dieser Enum-Konstanten vor.

Hinzufügen von Daten und Verhaltensweisen zu einer typsicheren Aufzählung

Sie können einer typsicheren Aufzählung Daten (in Form von Feldern) und Verhaltensweisen (in Form von Methoden) hinzufügen. Angenommen, Sie müssen eine Aufzählung für kanadische Münzen einführen und diese Klasse muss die Möglichkeit bieten, die Anzahl der in einer beliebigen Anzahl von Pennys enthaltenen Nickel, Groschen, Viertel oder Dollar zurückzugeben. Listing 2 zeigt Ihnen, wie Sie diese Aufgabe ausführen.

Listing 2: TEDemo.java(Version 2)

enum Coin {NICKEL (5), // Konstanten müssen zuerst erscheinen DIME (10), QUARTER (25), DOLLAR (100); // das Semikolon ist erforderlich private final int valueInPennies; Coin (int valueInPennies) {this.valueInPennies = valueInPennies; } int toCoins (int pennies) {return pennies / valueInPennies; }} öffentliche Klasse TEDemo {public static void main (String [] args) {if (args.length! = 1) {System.err.println ("Verwendung: Java TEDemo amountInPennies"); Rückkehr; } int pennies = Integer.parseInt (args [0]); für (int i = 0; i <Coin.values ​​(). length; i ++) System.out.println (Pennies + "Pennies enthält" + Coin.values ​​() [i] .toCoins (Pennies) + "" + Coin .values ​​() [i] .toString (). toLowerCase () + "s"); }}

Listing 2 deklariert zuerst eine CoinAufzählung. Eine Liste parametrisierter Konstanten identifiziert vier Arten von Münzen. Das an jede Konstante übergebene Argument gibt die Anzahl der Pennys an, die die Münze darstellt.

Das an jede Konstante übergebene Argument wird tatsächlich an den Coin(int valueInPennies)Konstruktor übergeben, der das Argument im valuesInPenniesInstanzfeld speichert . Auf diese Variable wird über die toCoins()Instanzmethode zugegriffen . Es teilt sich in die Anzahl der Pennys auf, die an toCoin()den penniesParameter übergeben werden, und diese Methode gibt das Ergebnis zurück, das zufällig die Anzahl der Münzen in der durch die CoinKonstante beschriebenen Geldmenge ist.

Zu diesem Zeitpunkt haben Sie festgestellt, dass Sie Instanzfelder, Konstruktoren und Instanzmethoden in einer typsicheren Enumeration deklarieren können. Schließlich ist eine typsichere Aufzählung im Wesentlichen eine spezielle Art von Java-Klasse.

Die Methode der TEDemoKlasse main()überprüft zunächst, ob ein einzelnes Befehlszeilenargument angegeben wurde. Dieses Argument wird durch Aufrufen der Methode der java.lang.IntegerKlasse in eine Ganzzahl konvertiert parseInt(), die den Wert ihres Zeichenfolgenarguments in eine Ganzzahl analysiert (oder eine Ausnahme auslöst, wenn eine ungültige Eingabe erkannt wird). Ich werde Integerin einem zukünftigen Java 101- Artikel mehr über und seine Cousin-Klassen zu sagen haben .

In main()Zukunft werden Coindie Konstanten durchlaufen . Da diese Konstanten in einem Coin[]Array gespeichert sind , wird main()ausgewertet Coin.values().length, um die Länge dieses Arrays zu bestimmen. Für jede Iteration des Schleifenindex i, main()wertet Coin.values()[i]die zuzugreifen Coinkonstant. Es ruft jeder toCoins()und toString()auf dieser Konstante, die weiter beweist , dass Coineine besondere Art von Klasse.

Kompilieren Sie den Quellcode wie folgt:

javac TEDemo.java

Führen Sie die kompilierte Anwendung wie folgt aus:

Java TEDemo 198

Sie sollten die folgende Ausgabe beachten:

198 Pfennige enthalten 39 Nickel 198 Pfennige enthalten 19 Groschen 198 Pfennige enthalten 7 Viertel 198 Pfennige enthalten 1 Dollar

Die Klasse erkundenEnum

The Java compiler considers enum to be syntactic sugar. Upon encountering a typesafe enum declaration, it generates a class whose name is specified by the declaration. This class subclasses the abstract Enum class, which serves as the base class for all typesafe enums.

Enum’s formal type parameter list looks ghastly, but it’s not that hard to understand. For example, in the context of Coin extends Enum, you would interpret this formal type parameter list as follows:

  • Any subclass of Enum must supply an actual type argument to Enum. For example, Coin’s header specifies Enum.
  • The actual type argument must be a subclass of Enum. For example, Coin is a subclass of Enum.
  • A subclass of Enum (such as Coin) must follow the idiom that it supplies its own name (Coin) as an actual type argument.

Examine Enum’s Java documentation and you’ll discover that it overrides java.lang.Object's clone(), equals(), finalize(), hashCode(), and toString() methods. Except for toString(), all of these overriding methods are declared final so that they cannot be overridden in a subclass:

  • clone() is overridden to prevent constants from being cloned so that there is never more than one copy of a constant; otherwise, constants could not be compared via == and !=.
  • equals()wird überschrieben, um Konstanten über ihre Referenzen zu vergleichen. Konstanten mit denselben Identitäten ( ==) müssen denselben Inhalt haben ( equals()), und unterschiedliche Identitäten implizieren unterschiedliche Inhalte.
  • finalize() wird überschrieben, um sicherzustellen, dass Konstanten nicht finalisiert werden können.
  • hashCode()wird überschrieben, weil equals()überschrieben wird.
  • toString() wird überschrieben, um den Namen der Konstante zurückzugeben.

Enumbietet auch seine eigenen Methoden. Diese Verfahren umfassen die finalcompareTo() ( Enumimplementiert die java.lang.ComparableSchnittstelle) getDeclaringClass(), name()und ordinal()Methoden: