Enumerationen

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 © 2025 Thomas Smits