Enumerationen

Das Problem

Wie kann ich …

  • … einen festen Satz von Werten vorgeben?
  • … dafür sorgen, dass nur diese Werte und kein anderer Wert übergeben werden kann?
  • … die Werte typsicher machen?
  • … mir dabei die Überprüfung vom Compiler abnehmen lassen?
  • … die Werte in gängigen Kontrollstrukturen verwenden?

In der fortgeschrittenen Programmierung benötigt man sehr häufig Methoden, bei denen über einen Parameter eine Auswahl getroffen werden kann, die sich aus einer begrenzten Menge von Möglichkeiten bestimmt. Beispiele sind die Auswahl einer Textauszeichnung (fett, kursiv, unterstrichen) in einer Textverarbeitung oder ein Wochentag in einem Kalender. In beiden Fällen kann man zwar einen int verwenden und einfach festlegen, welche Zahl welche Bedeutung haben soll, dies ist aber nicht elegant und vor allem fehleranfällig.

Eigentlich möchte man eine Möglichkeit, die benötigten Konstanten für die Übergabe zu definieren und der Compiler soll sicherstellen, dass beim Aufruf nur eine dieser Konstanten verwendet werden kann (typsicher).

Schön wäre es zusätzlich noch, wenn man diese Konstanten in allen Kontrollstrukturen (if und case) verwenden könnte.

Beispiel: Das Problem

public class Wochentag {
    public static final int MONTAG = 1;
    public static final int DIENSTAG = 2;
    public static final int MITTWOCH = 3;
    public static final int DONNERSTAG = 4;
    public static final int FREITAG = 5;
    public static final int SAMSTAG = 6;
    public static final int SONNTAG = 7;
}
public class DateJuggler {
    public void setWochentag(int wochentag) { ... }
    public int getWochentag() { ... }
}

DateJuggler dj = new DateJuggler();
dj.setWochentag(Wochentag.FREITAG);
dj.setWochentag(Wochentag.FREITAG + 100);
int wochentag = dj.getWochentag();

Das Beispiel zeigt das Problem, wenn man keine Unterstützung für für die oben erwähnten Konstanten in der Programmiersprache hat. Es gibt eine ganze Reihe von Fehlerquellen:

  1. Bei der Definition der Konstanten (MONTAG, DIENSTAG, …) muss man aufpassen, dass man keinen der Werte doppelt verwendet. Würde man SONNTAG aus Versehen ebenfalls auf 6 setzen, käme es zu subtilen, schwer zu findenden Fehlern.
  2. Man kann nicht sicherstellen, dass der Verwender nur eine der vorhandenen Konstanten verwendet. Da die Methode setWochentag einen int nimmt, kann man jede Zahl übergeben, auch unsinnige Tage wie 10 oder FREITAG + 100. 3. Innerhalb der Methode setWochentag muss man also durch eine Überprüfung noch einmal feststellen, ob der übergebende Wert im gewünschten Bereich liegt. Wenn nicht, muss man entweder eine Ausnahme werfen oder aber einen Standardwert annehmen.

Die Lösung: Enumerationen (Enums)

Enumeration (enumeration)

  • Konstrukt, um festen Satz von Konstanten zu definieren
  • Typsicher und vom Compiler überprüft
  • Funktionieren mit if und switch
  • Syntax: [Sichtbarkeit] enum { CONST1, CONST2, ... ; }
public enum Planet {
    MERKUR, VENUS, ERDE, MARS,
    JUPITER,SATURN, URANUS, NEPTUN;
    // PLUTO;
}

Java bietet mit den sogenannten Enumerationen eine Lösung für das beschriebene Problem. Mit ihnen kann man einen festen Satz von Konstanten definieren, wobei der Compiler überprüft, dass ein Verwender nur die erlaubten Konstanten übergeben kann.

In dem hier gegebenen Beispiel sehen wir eine Enumeration (Enum) mit dem Namen Planet, die acht Enum-Konstanten (nämlich die Planeten) enthält.

Technisch gesehen ist eine Enumeration einer Klasse sehr ähnlich. Sie führt einen neuen Typ ein (hier Planet) und erzeugt automatisch Objekte für die vorhandenen Konstanten (hier MERKUR, VENUS, ERDE, …). Sie wird hinter den Kulissen tatsächlich als Klasse realisiert, wobei es aber einige Besonderheiten gibt, die später noch erläutert werden. Zu jeder Enumeration gibt es also:

  1. Eine Klasse für die Enumeration
  2. Genau ein Objekt pro Konstante, die in der Enumeration deklariert wurde

Da eine Enumeration einen neuen Typ einführt, kann man von ihrem Typ Referenzvariablen deklarieren und damit auf die Konstanten-Objekte zeigen: Planet p = Planet.MERKUR

Beispiel: Mit Enumerationen

public enum Wochentag {
    MONTAG, DIENSTAG, MITTWOCH, DONNERSTAG, FREITAG, SAMSTAG, SONNTAG;
}

public class DateJuggler {
    public void setWochentag(Wochentag wochentag) { ... }
    public Wochentag getWochentag() { ... }
}

DateJuggler dj = new DateJuggler();
dj.setWochentag(Wochentag.FREITAG);
dj.setWochentag(Wochentag.FREITAG + 100);  // FEHLER!!

Wochentag wochentag = dj.getWochentag();

Dieses Beispiel zeigt noch einmal die Klasse DateJuggler, jetzt aber mithilfe von Enumerationen realisiert. Die Konstanten befinden sich jetzt in der neu geschaffenen Enumeration Wochentag. Die setWochentag-Methode bekommt keinen int mehr übergeben, sondern eine der Konstanten aus Wochentag, was durch den Typ des Parameters angezeigt wird.

Der Verwender von setWochentag kann jetzt die Methode nur noch mit einer der vorhandenen Konstanten aufrufen, jeder Versuch etwas anderes zu übergeben führt zu einem Fehler.

Eigenschaften von Enumerationen

  • Können nicht von anderen Klasse ableiten, sondern werden vom Compiler automatisch von Enum abgeleitet
  • Können Felder, Konstanten und Methoden enthalten
  • Enum-Konstanten sind echte Java-Objekte
  • Enum-Konstanten sind Singletons
    • es gibt zu jeder Enum-Konstante immer nur ein einziges Objekt
    • dürfen mit == verglichen werden
    • können nicht mit new erzeugt werden
    • können nur private oder default-sichtbare Konstruktoren haben
  • Enum-Konstanten können statisch importiert werden

Alle Enumerationen sind automatisch von java.lang.Enum abgeleitet. Daher kann man in der Deklaration einer Enumeration auch kein extends angeben. Die Klasse heißt Enum und nicht Enumeration, weil es bei ihrer Einführung bereits eine Klasse mit dem Namen Enumeration gab und man diesen daher nicht mehr verwenden konnte.

Der Konstruktor einer Enumeration muss private oder default-Sichtbarkiet haben. Dies ist nötig, da andernfalls die Eigenschaft der Konstanten als Singleton unterwandert würde.

Wie man an dieser Aufzählung bereits sieht, gehen die Eigenschaften von Java-Enumerationen weit über das hinaus, was in anderen Programmiersprachen möglich ist. So werden z. B. in C# und C++ Enumerationen einfach auf int-Werte abgebildet, wohingegen in Java das sog. „type safe Enumeration Pattern“ mit echten Objekten und einem reichhaltigen Satz von Features zur Verfügung steht.

Einige der Möglichkeiten, die Enumerationen bieten sind obskur, so die Möglichkeit abstrakte Methoden zu definieren (wird später noch erläutert). Man muss – und sollte – diese aber nicht unbedingt verwenden. Davon abgesehen sind Enumerationen sehr nützlich.

Beispiel: Enum mit Konstruktor

public enum Planet {
    MERKUR(59e+6, 88), VENUS(108e+6, 225),
    ERDE(150e+6, 365), MARS(228e+6, 1.9*365),
    JUPITER(778e+6, 11.9*365), SATURN(1427e+6, 29.5*365),
    URANUS(2870e+6, 84*365),  NEPTUN(4497e+6, 165*365);

    private final double entfernungsonne; // in km
    private final double umlaufzeit;      // in tagen

    private Planet(double entfernungsonne, double umlaufzeit) {
        this.entfernungsonne = entfernungsonne;
        this.umlaufzeit = umlaufzeit;
    }

    public double bahngeschwindigkeit() {
        return 2 * entfernungsonne * Math.PI / umlaufzeit / 24;
    }
}

Man kann einer Enumeration Daten mitgeben, d. h. die Konstanten tragen dann neben ihrem Namen noch beliebige weitere Informationen. Dies kann sehr nützlich sein, wenn es Werte gibt, die direkt zu den Konstanten gehören. Im Beispiel sind das die Bahngeschwindigkeit der Planeten und ihre Entfernung zur Sonne.

Die Enum-Konstanten (z. B. MERKUR) geben dann bei ihrer Deklaration die Werte an, die in den Attributen des Objektes gespeichert werden sollen. Technisch gesehen handelt es sich also um einen Konstruktoraufruf, nur dass das new

implizit erzeugt wird.

Beispiel: Iterieren über eine Enum

public class Ausgabe {

    public static void main(String[] args) {

        for (Planet planet : Planet.values()) {
            System.out.printf("%s -> %,.0f km/h%n",
                    planet.name(),
                    planet.bahngeschwindigkeit());
        }
    }
}
MERKUR -> 175.525 km/h                      VENUS -> 125.664 km/h
ERDE -> 107.589 km/h                        MARS -> 86.071 km/h
JUPITER -> 46.893 km/h                      SATURN -> 34.696 km/h
URANUS -> 24.506 km/h                       NEPTUN -> 19.549 km/h

Jede Enumeration hat eine statische Methode values(), die ein Array mit allen vorhandenen Konstanten zurückliefert. Will man also ein Programm schreiben, dass über alle Konstanten iteriert und dann nicht verändert werden muss, wenn man eine weitere Konstante einführt, bietet sich diese Methode an. Über name() kann man dann den Namen der Konstante wieder auslesen.

Das Beispiel nutzt genau dies aus. Es läuft mit einer for-each-Schleife über alle Konstanten, liest dann den Namen aus und ruft eine Methode (bahngeschwindigkeit()) auf.

Beispiel: Enums und if / switch

public class SpaceTravel {
    private static final Planet HOME_PLANET = Planet.ERDE;

    public void travel(Planet zielPlanet) {

        if (zielPlanet == HOME_PLANET) {
            return; // wir sind doch schon da
        }

        switch (zielPlanet) {
            case JUPITER: // langer Flug
                break;
            case MARS:    // kurzer Flug
                break;
             ...
        }
    }
}

Da es sich bei Enumerationen um Singletons handelt, darf man sie – anders als alle anderen Objekte in Java – mit == vergleichen. Zusätzlich besteht noch die Möglichkeit, sie in switch-Verzweigungen zu verwenden.

Beachtenswert ist, dass man bei den case-Statements nur den Namen der Konstante ohne vorangestellten Namen der Enumeration benutzen muss. Der Grund liegt darin, dass bereits aufgrund des Typs der Variable im switch klar ist, um welche Enumeration es sich handelt (zielPlanet ist vom Typ Plant, damit ist klar, wo der Compiler nach JUPITER und MARS suchen muss).

Wichtige Methoden von Enumerationen

  • public final String name() – Liefert den Namen der Konstante, genauso wie sie in der Datei angegeben wurde
  • public final int ordinal() – Liefert die Ordinalzahl der Konstante (sollte normalerweise nicht verwendet werden)
  • public String toString() – Liefert den Namen der Konstante, kann überschrieben werden, um besser lesbare Namen zurückzugeben
  • public static Enum valueOf(String name) – Wandelt den Namen der Konstanten (wie von name() zurückgeliefert) wieder in eine Enum um
  • public static Enum[] values() – Gibt ein Array mit allen vorhandenen Enum-Konstanten zurück
  • public final int compareTo(Enum o) – Vergleicht Enums anhand ihrer Ordinalzahlen
  • public final boolean equals(Object other) – Entspricht Object.equals()und damit ==
  • public final int hashCode() – Entspricht Object.hashCode()
  • protected final Object clone() – Darf nicht verwendet werden, da Enums Singletons sind

Da alle Enumerationen von der Klasse Enum abgeleitet sind, erben sie entsprechende Methoden. Die meisten Methoden sind sofort verständlich. Wichtig ist der Unterschied zwischen name() und toString(). Beide liefern standardmäßig den Namen der Konstante, wie er in der Enumeration deklariert wurde, zurück. toString() kann man aber überschreiben, name() nicht. Insofern kann man toString() verwenden, um einen eigenen Namen zurückzugeben während name() immer unverändert bleibt und exakt den Namen der Konstante entsprechend ihrer Deklaration zurück gibt.

Da es sich bei Enumerationen immer um Singletons handelt, sollten die Methoden hashCode(), equals(), clone() und compareTo() nicht verändert werden. Deshalb sind sie als final deklariert, um ein versehentliches Überschreiben zu verhindern.

Beispiel: Methoden auf Enums

String name = Planet.ERDE.name();

Planet planet = Planet.valueOf(name);

System.out.printf("Wir leben auf der %s, sie ist " +
        "der %d Planet%n", planet, planet.ordinal() + 1);

System.out.printf("Wir haben %d Planeten im Sonnensystem%n",
        Planet.values().length);
Wir leben auf der ERDE, sie ist der 3 Planet
Wir haben 8 Planeten im Sonnensystem

Das Beispiel verwendet die eingebauten Methoden, die jede Enumeration hat. Es wird gezeigt, dass man den Namen einer Konstante (Planet.ERDE.name()) über die statische valueOf()-Methode wieder in eine Referenz auf die Konstante umwandeln kann.

Man sieht auch, dass die von ordinal() gelieferten Werte bei 0 beginnen, sodass im Beispiel noch eine 1 addiert werden muss.

Abstrakte Methoden

  • Enums können abstrakte Methoden enthalten
  • Diese Methoden müssen für jede Enum-Konstante implementiert werden
  • Mit dieser Konstruktion sollen unübersichtliche case bzw. if-Konstrukte vermieden werden

Die Möglichkeit in einer Enumeration abstrakte Methoden zu definieren, um diese dann in den Konstanten zu implementieren, ist ausgesprochen obskur und verwirrend. Wenn man sich Enumerationen als Klassen und die Konstanten als Objekte vorstellt bedeutet dies, dass die Objekte (sic!) die Methoden implementieren, die die Klasse als abstrakt definiert hat. Aus objektorientierter Sicht ist dies unsinnig, da nur Klassen, aber nicht Objekte Methoden tragen können.

Technisch löst Java diesen Widerspruch indem für jede Konstante eine eigene Subklasse der Enumeration generiert wird und diese dann die entsprechende Methodenimplementierung enthält.

Wegen seiner seltsamen Syntax sollte man dieses Feature nur einsetzen, wenn es einen wirklichen Vorteil bringt.

Beispiel: Abstrakte Methoden

public enum Operation {
    ADDITION {
        public double anwenden(double op1, double op2) {
            return op1 + op2;
        }
    },
    SUBTRAKTION {
        public double anwenden(double op1, double op2) {
            return op1 - op2;
        }
    },
    MULTIPLIKATION {
        public double anwenden(double op1, double op2) {
            return op1 * op2;
        }
    };
    public abstract double anwenden(double op1, double op2);
}

Das Beispiel zeigt, wie die Konstante die abstrakte Methode anwenden implementieren. Abhängig von der Konstante führt die Methoden unterschiedliche Rechenoperationen durch.

public class Rechner {

    public double rechne(double op1, double op2, Operation operation) {
        return operation.anwenden(op1, op2);
    }

    public static void main(String[] args) {
        Rechner r = new Rechner();
        double summe = r.rechne(5, 5, Operation.ADDITION);
        double differenz = r.rechne(6, 3, Operation.SUBTRAKTION);
        double produkt = r.rechne(6, 3, Operation.MULTIPLIKATION);
        System.out.printf("%.0f, %.0f, %.0f", summe, differenz, produkt);
    }
}
10, 3, 18

Copyright © 2026 Thomas Smits