Java Enums
Definiere eine feste Menge von Konstanten in Java mit enum-Typen, einschließlich Enums mit Feldern, Konstruktoren und Methoden.
Ein enum ist eine Klasse, deren Instanzen eine zur Kompilierzeit bekannte, feste Menge von Werten bilden. Wochentage, Ampelfarben, Bestellstatus — alles, bei dem die zulässigen Werte eine kleine, benannte Liste sind. Wenn du einmal enum Status { OPEN, CLOSED } schreibst, sind das die einzigen Status-Werte, die jemals existieren werden, und der Compiler meldet es, wenn du versuchst, einen dritten zu erfinden.
Enums ersetzen die alte C-Gewohnheit von public static final int OPEN = 0;. Integer-Konstanten bieten keine Typsicherheit — setStatus(7) kompiliert — aber setStatus(Status status) akzeptiert nur einen der deklarierten Werte.
Einen enum deklarieren
Die einfachste Form ist eine Liste von Konstantennamen:
public enum Direction {
NORTH, EAST, SOUTH, WEST
}Verwende ihn wie jeden anderen Typ:
Direction d = Direction.NORTH;
if (d == Direction.NORTH) System.out.println("heading up");Jede Konstante ist eine Singleton-Instanz von Direction. Der Vergleich mit == ist korrekt und idiomatisch — es gibt immer nur ein NORTH in der JVM, also stimmt Referenzgleichheit mit Wertgleichheit überein.
In einem switch
Enums und switch sind füreinander gemacht:
switch (d) {
case NORTH -> System.out.println("up");
case SOUTH -> System.out.println("down");
case EAST, WEST -> System.out.println("sideways");
}Beachte das bloße NORTH statt Direction.NORTH — innerhalb eines switch auf einem enum kennt der Compiler den Typ. Wenn du eine neue Konstante hinzufügst und vergisst, sie in einem switch-Ausdruck zu behandeln, erhältst du einen Kompilierfehler wegen fehlender Vollständigkeit, was genau das gewünschte Sicherheitsnetz ist.
Felder, Konstruktoren und Methoden
Ein enum kann Daten und Verhalten tragen. Jede Konstante wird mit Argumenten konstruiert, und der enum-Rumpf kann normale Methoden definieren:
public enum Planet {
MERCURY(3.303e+23, 2.4397e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6);
private final double massKg;
private final double radiusM;
Planet(double massKg, double radiusM) {
this.massKg = massKg;
this.radiusM = radiusM;
}
public double surfaceGravity() {
final double G = 6.67300E-11;
return G * massKg / (radiusM * radiusM);
}
}Zwei Regeln zum Merken:
- Die Konstantenliste steht zuerst im Rumpf, vom Rest durch ein Semikolon getrennt.
- Der Konstruktor ist implizit private — Enums können von außen nicht instanziiert werden, was der eigentliche Zweck ist.
Eingebaute Methoden
Jeder enum erhält einige Methoden kostenlos:
name()gibt den deklarierten Namen der Konstante alsStringzurück.ordinal()gibt ihre nullbasierte Position in der Deklaration zurück. Gelegentlich nützlich; vermeide es, ihn zu persistieren, da das Umordnen von Konstanten die Bedeutung still ändert.values()gibt ein Array aller Konstanten in Deklarationsreihenfolge zurück — ideal fürfor (Direction d : Direction.values())-Schleifen. Es erstellt bei jedem Aufruf ein neues Array, also cache es in einer lokalen Variable, wenn du in einer heißen Schleife iterierst.valueOf(String)sucht eine Konstante nach ihrem genauen Namen (Groß-/Kleinschreibung beachten) und wirftIllegalArgumentException, wenn keine Übereinstimmung gefunden wird.
Da valueOf bei unbekannter Eingabe wirft, schütze es, wenn der String von außerhalb deines Codes kommt — Benutzereingabe, eine Datei, eine Netzwerknutzlast:
Direction d = Direction.valueOf("EAST"); // EAST
try {
Direction.valueOf("UP"); // not a declared constant
} catch (IllegalArgumentException e) {
System.out.println("no such constant"); // no such constant
}Konstantenspezifisches Verhalten
Manchmal muss sich eine Konstante anders verhalten als die anderen. Du kannst Methoden pro Konstante mit einem anonymen Rumpf überschreiben:
public enum Operation {
PLUS { public int apply(int a, int b) { return a + b; } },
MINUS { public int apply(int a, int b) { return a - b; } },
TIMES { public int apply(int a, int b) { return a * b; } };
public abstract int apply(int a, int b);
}Jede Konstante wird zu einer kleinen anonymen Unterklasse von Operation, die apply implementiert. Das Strategy-Pattern-Gefühl ist inklusive.
Interfaces implementieren
Enums können Interfaces implementieren — sie können jedoch keine anderen Klassen erweitern (sie erweitern implizit java.lang.Enum):
public enum Day implements Runnable {
MONDAY, TUESDAY;
public void run() { System.out.println("today is " + name()); }
}Dies ist eine saubere Möglichkeit, eine feste Familie von Strategien in Code einzubinden, der ein beliebiges Runnable erwartet.
EnumSet und EnumMap
Wenn du ein Set oder eine Map mit einem enum als Schlüssel benötigst, bevorzuge EnumSet und EnumMap gegenüber HashSet/HashMap. Beide werden durch einen Bit-Vektor oder ein einfaches, auf den enum zugeschnittenes Array unterstützt, sodass Hashing vollständig vermieden wird — Suchen sind ein einzelner Array-Index, und ein EnumSet eines kleinen enums passt in ein einziges long:
EnumSet<Direction> horizontal = EnumSet.of(Direction.EAST, Direction.WEST);
EnumSet<Direction> all = EnumSet.allOf(Direction.class); // every constant
EnumSet<Direction> none = EnumSet.noneOf(Direction.class); // empty, ready to fill
EnumMap<Direction, String> labels = new EnumMap<>(Direction.class);
labels.put(Direction.NORTH, "up");Die Iteration über ein EnumSet oder eine EnumMap folgt der Deklarationsreihenfolge, was ihre Ausgabe deterministisch macht — ein weiterer Grund, sie den hash-basierten Sammlungen vorzuziehen.
Ein ausgearbeitetes Beispiel
Was kommt als Nächstes
Enums schränken eine feste Menge von Instanzen ein. Das nächste Kapitel stellt eine andere Art von eingeschränkter Klasse vor — records, die für einfache unveränderliche Datenträger konzipiert sind. Weiter zu Java records.