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
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.String
Konstanten 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, String
leiden 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 switch
Anweisungen verwendet werden.
Betrachten Sie das folgende Beispiel für das typsichere Aufzählungsmuster. Die Suit
Klasse 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 Suit
Variable ein und weisen sie Suit
wie folgt einer der Konstanten zu:
Anzug Anzug = Anzug.DIAMONDS;
Vielleicht möchten Sie dann suit
eine switch
Aussage 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.CLUBS
einen 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 Suit
in einem Paket, importiert das Paket, und statisch diese Konstanten importiert, würde der Compiler beschweren sich, dass es nicht konvertieren kann , Suit
um int
bei der Begegnung suit
in switch(suit)
. In Bezug auf jede case
würde der Compiler auch melden, dass ein konstanter Ausdruck erforderlich ist.
Java unterstützt das Typesafe Enum-Muster mit switch
Anweisungen 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 Direction
eine 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
, EAST
und SOUTH
ENUM - Konstanten werden als konstant spezifische Klasse Stellen durchgeführt , die anonyme Klassen Erweiterung der umschließenden definieren Direction
Klasse.
Direction
und 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 switch
Anweisung. 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 Direction
typsichere Aufzählung und iteriert über die konstanten Elemente, die values()
zurückgegeben werden. Für jeden Wert switch
wählt die Anweisung (erweitert, um typsichere Aufzählungen zu unterstützen) case
diejenige aus, die dem Wert von entspricht, d
und gibt eine entsprechende Nachricht aus. (Sie stellen einer Aufzählungskonstante, z. B. NORTH
ihrem Aufzählungstyp, kein Präfix voran .) Schließlich wird in Listing 1 ausgewertet, Direction.NORTH.compareTo(Direction.SOUTH)
um festzustellen, ob sie NORTH
vorher 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 NORTH
kommt SOUTH
bei 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 Coin
Aufzä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 valuesInPennies
Instanzfeld speichert . Auf diese Variable wird über die toCoins()
Instanzmethode zugegriffen . Es teilt sich in die Anzahl der Pennys auf, die an toCoin()
den pennies
Parameter übergeben werden, und diese Methode gibt das Ergebnis zurück, das zufällig die Anzahl der Münzen in der durch die Coin
Konstante 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 TEDemo
Klasse main()
überprüft zunächst, ob ein einzelnes Befehlszeilenargument angegeben wurde. Dieses Argument wird durch Aufrufen der Methode der java.lang.Integer
Klasse 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 Integer
in einem zukünftigen Java 101- Artikel mehr über und seine Cousin-Klassen zu sagen haben .
In main()
Zukunft werden Coin
die 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 Coin
konstant. Es ruft jeder toCoins()
und toString()
auf dieser Konstante, die weiter beweist , dass Coin
eine 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 toEnum
. For example,Coin
’s header specifiesEnum
. - The actual type argument must be a subclass of
Enum
. For example,Coin
is a subclass ofEnum
. - A subclass of
Enum
(such asCoin
) 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, weilequals()
überschrieben wird.toString()
wird überschrieben, um den Namen der Konstante zurückzugeben.
Enum
bietet auch seine eigenen Methoden. Diese Verfahren umfassen die final
compareTo()
( Enum
implementiert die java.lang.Comparable
Schnittstelle) getDeclaringClass()
, name()
und ordinal()
Methoden: