Angelika Langer - Training & Consulting
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | NEWSLETTER | CONTACT | Twitter | Lanyrd | Linkedin
 
HOME 

  OVERVIEW

  BY TOPIC
    JAVA
    C++

  BY COLUMN
    EFFECTIVE JAVA
    EFFECTIVE STDLIB

  BY MAGAZINE
    JAVA MAGAZIN
    JAVA SPEKTRUM
    JAVA WORLD
    JAVA SOLUTIONS
    JAVA PRO
    C++ REPORT
    CUJ
    OTHER
 

GENERICS 
LAMBDAS 
IOSTREAMS 
ABOUT 
NEWSLETTER 
CONTACT 
Aufzählungstypen - Enumeration Types

Aufzählungstypen - Enumeration Types
Aufzählungstypen (Enum-Typen)

JavaSPEKTRUM, November 2006
Klaus Kreft & Angelika Langer

Dies ist das Manuskript eines Artikels, der im Rahmen einer Kolumne mit dem Titel "Effective Java" im JavaSPEKTRUM erschienen ist.  Die übrigen Artikel dieser Serie sind ebenfalls verfügbar ( click here ).

Die Version 5.0 des JDK wurde im September 2004 freigegeben und hat die Java-Entwickler mit allerlei neuen Sprachmitteln beglückt.  In diesem und den nächsten Beiträgen in dieser Kolumne wollen wir uns die wichtigsten Neuerungen ansehen und dabei – wie immer in dieser Kolumne – auf die Stolpersteine eingehen.  Wir beginnen die Serie mit einer Einführung in die Aufzählungstpyen.
 

Historie

Enum-Typen sind Aufzählungstypen von unveränderlichen Werten.  Solche Aufzählungstypen gab es bereits in den Vorgängersprachen C und C++, von denen Java einiges geerbt hat - allerdings nicht die Aufzählungstypen.  Beim Design der Sprache Java war man der Meinung, daß Aufzählungstypen ein überflüssiges Sprachmittel seien. Unveränderliche Werte kann man schließlich auch durch final-Konstanten ausdrücken.  Also wozu braucht man Enum-Typen?   Mit der Zeit hat sich jedoch herausgestellt, daß Aufzählungstypen in der Praxis recht häufig vorkommen und viele Entwickler haben versucht, einen Aufzählungstypen als Klasse mit final-Feldern auszudrücken.  Spätestens seit Joshua Bloch in seinen populären Buch "Effective Java" (das zufälllig den Titel unserer Kolumne trägt ;-) eine solche "Enum-Klasse" beschrieben hat, ist diese Technik allgemein etabliert.  Für die Version 5.0 von Java haben Joshua Bloch und andere die Einbettung von Aufzählungstypen in die Sprache vorgeschlagen und spezifiziert.  Deshalb gibt es nun auch in Java neben Klassen und Interfaces auch noch Enum-Typen.

Einfache Enum-Typen

Im einfachsten Fall ist ein Enum-Typ eine Aufzählungstypen von unveränderlichen Werten, die beispielsweise so aussehen könnte:
public enum Season { winter, spring, summer, fall }
enum ist ein neues Schlüsselwort in der Sprache und  dient dazu, dem Compiler die Definition eines neuen Typs bekannt zu geben. Es ist vergleichbar mit den Schlüsselworten class und interface, die ebenfalls die Definition von neuen Typen einleiten.  Der im obigen Beispiel definierte Typ heißt Season und es gibt genau 4 Objekte von diesem Typ, nämlich die Objekte winter, spring, summer und fall.  Weitere Objekte vom Typ Season können und sollen nicht erzeugt werden.  Alle 4 Objekte repräsentieren "Konstanten", d.h. die Objekte sind unveränderlich - jedenfalls vom Konzept her.  Dass sie nicht wirklich unveränderlich sind, sehen wir später noch, wenn wir die Details anschauen.

Solche Aufzählungen von Werten hat man traditionell in Java mit static final int-Werten implementiert.  Das sieht dann so aus:

public class Season {
  public static final int winter = 0;
  public static final int spring = 1;
  public static final int summer = 2;
  public static final int fall   = 3;
}
Das Ergebnis ist ebenfalls ein Typ Season mit 4 konstanten Werten winter, spring, summer und fall.  In beiden Fällen kann man switch-Anweisungen schreiben mit Zweigen für die 4 Werte.  Die Anweisungen sehen fast identisch aus.  Traditionell sähe es so aus:
public String getGermanName(int season) {
  switch (season) {
    case Season.winter: return "Winter";  break;
    case Season.spring: return "Frühling";  break;
    case Season.summer: return "Sommer";  break;
    case Season.fall  : return "Herbst";  break;
    default           : throw new IllegalArgumentException();
  }
}
Mit den neuen Enum-Typen sieht es so aus:
public String getGermanName(Season season) {
  switch (season) {
    case Season.winter: return "Winter";  break;
    case Season.spring: return "Frühling";  break;
    case Season.summer: return "Sommer";  break;
    case Season.fall  : return "Herbst";  break;
  }
}
Bislang konnte ein switch nur über eine int-Variable gemacht werden.  In Java 5.0 sind auch enum-Variablen erlaubt.  Außerdem entfällt der lästige default-Zweig, wenn es zu jedem Enum-Wert eine korrespondierende Funktionalität gibt.  Das ist anders als bei der traditionellen Lösung, bei der die emulierten Enum-Werte in Wirklichkeit von Integer-Zahlen repräsentiert werden.  Der Methode getGermanName(int season)  könnte beispielsweise die Zahl 10 übergeben werden, die keiner der definierten Season-Konstanten entspricht. Das kann bei den neuen Enum-Typen nicht vorkommen.

Die Enum-Typen haben noch allerlei andere Vorzüge.

  • Beispielsweise haben Sie automatisch eine toString()-Methode, die den symbolischen Namen des Objekts ausgibt.  Bei der Lösung mit final int Konstanten gibt System.out.println(Season.summer) den String "2" aus; beim  neuen Enum-Typ dagegen den String "summer".
  • Enum-Typen sind Referenztypen, d.h. man kann Enum-Objekte in Collections ablegen, was bei den traditionellen int-Konstanten nicht so direkt geht, weil sie von primitivem Typ sind.
Enum-Typen  kollidieren übrigens mit Klassen und Interfaces gleichen Namens, die im selben Package definiert sind.  Man kann daher nicht gleichzeitig eine Klasse Season und einen Aufzählungstyp Season im selben Package definieren.  Ein Blick hinter die Kulissen zeigt, daß Aufzählungstypen intern sogar als Klassen implementiert sind, was den Namenskonflikt erklärt.

Ein Blick hinter die Kulissen

Wir wollen an dieser Stelle gar nicht auf alle Eigenschaften der neuen Enum-Typen eingehen, sondern statt dessen einmal näher ansehen, was der Compiler aus einem Enum-Typ macht.  Das ist wichtig, um einige der Überraschungen zu verstehen, die man mit Enum-Typen erleben kann.

Der Compiler übersetzt die Definition eines Enum-Typen in eine Klassendefinition.  Im Byte-Code findet man deshalb keinen Enum-Typ Season, sondern eine Klasse Season.  Diese synthetische Klasse sieht so oder so ähnlich aus, wie in Listing 1 gezeigt.  Was der Compiler generiert, ist compiler-abhängig und kann im Detail von der hier gezeigten Implementierung abweichen.
 

 
public final class Season extends Enum<Season> {

    private Season(String s, int i)
    {
        super(s, i);
    }

    public static final Season[] values()
    {
        Season aseason[];
        int i;
        Season aseason1[];
        System.arraycopy(aseason = ENUM$VALUES, 0, aseason1 = new Season[i = aseason.length], 0, i);
        return aseason1;
    }

    public static final Season valueOf(String s)
    {
        Season aseason[];
        Season season;
        for(int i = (aseason = ENUM$VALUES).length; --i >= 0;)
            if(s.equals((season = aseason[i]).name()))
                return season;

        throw new IllegalArgumentException(s);
    }

    public static final Season winter;
    public static final Season spring;
    public static final Season summer;
    public static final Season fall;
    private static final Season ENUM$VALUES[];

    static 
    {
        winter = new Season("winter", 0);
        spring = new Season("spring", 1);
        summer = new Season("summer", 2);
        fall = new Season("fall", 3);
        ENUM$VALUES = (new Season[] {
            winter, spring, summer, fall
        });
    }
}

Listing 1: Synthetische Klasse, vom Compiler aus einer Enum-Typ-Deklaration generiert

Wie man sieht, sind alle aus Enum-Typen generierten Klassen von einer generischen Superklasse Enum abgeleitet, von der sie einige Methoden erben. Die Superklasse java.lang.Enum ist in Listing 2 gezeigt (obwohl man diese Klasse nicht direkt nutzen kann, ist sie auch in der JavaDoc des JDK beschrieben).
 

 
public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {
    private final String name;
    public final String name() {
 return name;
    }
    private final int ordinal;
    public final int ordinal() {
 return ordinal;
    }
    protected Enum(String name, int ordinal) {
 this.name = name;
 this.ordinal = ordinal;
    }

    public String toString() {
 return name;
    }
    public final boolean equals(Object other) { 
        return this==other;
    }
    public final int hashCode() {
        return System.identityHashCode(this);
    }
    protected final Object clone() throws CloneNotSupportedException {
 throw new CloneNotSupportedException();
    }
    public final int compareTo(E o) {
 Enum other = (Enum)o;
 Enum self = this;
 if (self.getClass() != other.getClass() && // optimization
            self.getDeclaringClass() != other.getDeclaringClass())
     throw new ClassCastException();
 return self.ordinal - other.ordinal;
    }
    public final Class<E> getDeclaringClass() {
 Class clazz = getClass();
 Class zuper = clazz.getSuperclass();
 return (zuper == Enum.class) ? clazz : zuper;
    }

    public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                                String name) {
        T result = enumType.enumConstantDirectory().get(name);
        if (result != null)
            return result;
        if (name == null)
            throw new NullPointerException("Name is null");
        throw new IllegalArgumentException(
            "No enum const " + enumType +"." + name);
    }
}

Listing 2: Die Enum-Superklasse java.lang.Enum

Wie man sieht, ist der Enum-Typ Season ein Referenztyp und alle Enum-Konstanten sind Objekte vom Typ Season.  An der Superklasse Enum kann man sehen, daß jedes Enum-Objekt aus einem String besteht, dem symbolischen Namen, und einem int-Wert, seiner Ordinalzahl.  Die vier Season-Objekte werden über statische Felder in der Klasse Season referenziert und diese Referenzen werden in einem Static-Initializer-Block initialisiert.  Es gibt also tatsächlich nur vier statische Season-Objekte.  Es können keine weiteren Season-Objekte erzeugt werden. Der Konstruktor der Klasse Season ist private und kann deshalb von Außen nicht aufgerufen werden. Er wird lediglich intern für die Erzeugung der vier statischen Objekte verwendet. Die clone()-Methode ist in der Superklasse Enum implementiert und wirft immer eine Exception.  Sie ist darüber hinaus als final deklariert, so daß diese Implementierung nicht überschrieben werden kann.  Enum-Objekte können also vom Benutzer weder konstruiert noch geklont werden. Außerdem ist die generierte Klasse final, so man auch durch ableiten bzw. überschreiben kein anderes Verhalten einbringen kann.

Infrastruktur.Methoden wie toString(), equals(), hashcode() und compareTo() sind bereits in der Enum-Superklasse definiert.  Daneben gibt es noch eine Reihe von Hilfsmethoden wie zum Beispiel eine values()-Methode, die ein Array mit allen Enum-Werten eines Enum-Typs liefert.

Komplexere Enum-Typen

Neben den ganz einfachen Aufzählungstypen wie unserem Season-Typ gibt es wesentlich komplexere Enum-Typen.  Java erlaubt es nämlich, dass Enum-Typen Felder und Methoden haben können.  Hier ist ein einfaches Beispiel:
public enum Month {
    january(31),
    february(28),
    march(31),
    april(30),
    may(31),
    june(30),
    july(31),
    august(31),
    september(30),
    october(31),
    november(30),
    december(31);

    private final int days;

    private Month(int days) {
        this.days = days;
    }

    public int days() {
        return days;
    }
}

Der Enum-Typ Month hat ein Feld, welches die Anzahl der Tage in jedem Monat enthält. Natürlich ist das Beispiel grob vereinfacht; wir haben die Schaltjahre schlicht ignoriert.  Das Feld days muß initialisiert werden.  Zu diesem Zweck hat der Enum-Typ Month einen Konstruktor, der die Anzahl der Tage zum Parameter hat.  Dieser Konstruktor  ist private, sonst könnten ja außerhalb des Enum-Types explizit neue Month-Objekte erzeugt werden und das soll verhindert werden. Der Konstruktor wird aufgerufen in der Aufzählung der einzelnen Enum-Werte.  Hinter dem jeweiligen symbolischen Namen folgen in runden Klammern das Argument bzw. die Argumente des Konstruktors. Das heißt, in dem Ausdruck january(31) ist january der symbolische Name des Enum-Werts und 31 ist das Argument des Konstruktors.

Daneben gibt es eine public Zugriffsmethode days(), die den Wert des days-Feldes liefert. In unserem Beispiel ist die Zugriffsmethode eine rein lesende Methode, die das Month-Objekt nicht modifiziert.  Das ist typisch für die Methoden von Enum-Typen, weil Enum-Werte konzeptionell unveränderliche Objekte sind.  Diese Unveränderlichkeit wird jedoch nicht erzwungen.  Prinzipiell wäre es möglich, daß ein Enum-Typ beliebig viele Felder hat und beliebige Methoden, die diese Felder ändern.  Dann wären die einzelnen Enum-Werte veränderlich und keine "Konstanten" mehr.  Das widerspricht zwar der eigentlichen Idee von Enum-Werten als symbolischen Konstanten, ist aber erlaubt.

Enum-Typen können auch noch komplexer sein und je Enum-Wert eine andere Implementierung für eine bestimmte Methode haben.  Das könnte dann so aussehen:

public enum Operation {
  plus  {double eval(double x, double y) { return x + y; }},
  minus {double eval(double x, double y) { return x - y; }},
  times {double eval(double x, double y) { return x * y; }},
  div   {double eval(double x, double y) { return x / y; }};

  // perform arithmetic operation represented by this constant
  abstract double eval(double x, double y);
}

Dieser Enum-Typ Operation hat 4 Werte: plus, minus, times und div und eine abstrakte Methode eval().  Jeder der Enum-Werte implementiert die abstrakte Methode auf eine andere Art und Weise.  In der nachfolgenden Methode würde dann für jeden Aufruf von eval()etwas anderes heraus kommen:
void f(double x, double y) {
  for (Operation op : Operation.values()) {
    System.out.println(x+" "+op+" "+y+" = "+op.eval(x, y));
  }
}
Intern in der generierten Klasse ist jeder der Enum-Werte von einem anderen Typ, in dem die Methode eval() jeweils anders implementiert ist.  Diese vier verschiedenen Typen sind in der Regel anonyme innere Klassen.
 
 
public abstract class Operation extends Enum {
    public static final Operation[] values()     {
        return (Operation[])$VALUES.clone();
    }
    public static Operation valueOf(String s)     {
        return (Operation)Enum.valueOf(nameclash/Operation, s);
    }
    private Operation(String s, int i)     {
        super(s, i);
    }

    abstract double eval(double d, double d1);

    public static final Operation plus;
    public static final Operation minus;
    public static final Operation times;
    public static final Operation div;
    private static final Operation $VALUES[];

    static 
    {
        plus = new Operation("plus", 0) {
            double eval(double d, double d1)             {
                return d + d1;
            }
        };
        minus = new Operation("minus", 1) {
            double eval(double d, double d1)             {
                return d - d1;
            }
        };
        times = new Operation("times", 2) {
            double eval(double d, double d1)             {
                return d * d1;
            }
        };
        div = new Operation("div", 3) {
            double eval(double d, double d1)              {
                return d / d1;
            }
        };
        $VALUES = (new Operation[] {
            plus, minus, times, div
        });
    }
}

Listing 3: Die aus dem Enum-Typ Operation generierteKlasse 

Wenn man also das Objekt Operation.minus mit getClass() nach seinem Type fragen würde, dann hätte es einen anderen Typ als das Objekt Operation.plus und beide Typen sind vom zugehörigen Enum-Typ Operation verschieden.  Wenn man wissen will, zu welchem Enum-Typ die Objekte Operation.plus und Operation.minus gehören, muß man die Methode getDeclaringClass() anstelle der Methode getClass() verwenden.  getDeclaringClass() ist eine der Methoden, die der Compiler automatisch für die generierte Enum-Klasse erzeugt.

Enum-Typen mit abstrakten Methoden haben wir in der Praxis noch nie gebraucht.  Hingegen ist die Möglichkeit, einem Enum-Typ Felder zu geben und entsprechende Zugriffsmethoden zu definieren, in der Praxis recht nützlich und wird relativ häufig verwendet.  Allerdings ist das Definieren und Verwenden solcher Enum-Typen bisweilen mit Überraschungen verbunden.  Ein solchen Beispiel mit Überraschungen sehen wir uns in der nächsten Ausgabe der Kolumne an.

Zusammenfassung

Die Enum-Typen sind eines der in Java 5.0 neuen Sprachmittel.  Ein Enum-Typ ist ein Typ, der eine fixe Anzahl von symbolischen Konstanten definiert.  Solche Aufzählungstypen, von denen es nur eine handvoll Objekte gibt, kommen in der Praxis relativ häufig vor. Ein Beispiel im JDK ist java.util.concurrent.TimeUnit - ein Typ der Zeiteinheiten wie Nanosekunden, Millisekunden, Sekunden, usw. repräsentiert.

Enum-Typen in Java können relativ komplex sein.  Sie können Felder und Methoden haben und sogar unterschiedliche Implementierungen von abstrakten Methoden je Enum-Werte haben.

Literaturverweise und weitere Informationsquellen

/JLS3/ The Java Language Specification, Third Edition
James Gosling, Bill Joy, Guy Steele, Gilad Bracha,
Addison Wesley, June 2005
URL: http://java.sun.com/docs/books/jls/

Die gesamte Serie über Enum-Typen:

/ENUM1/  Aufzählungstypen - Das Sprachmittel
Klaus Kreft & Angelika Langer
Java Spektrum, November 2006
URL: http://www.AngelikaLanger.com/Articles/EffectiveJava/28.Enums/28.Enums.html
/ENUM2/  Aufzählungstypen und ihre Fallstricke
Klaus Kreft & Angelika Langer
Java Spektrum, Januar 2007
URL: http://www.AngelikaLanger.com/Articles/EffectiveJava/29.EnumPitfall/29.EnumPitfall.htm

If you are interested to hear more about this and related topics you might want to check out the following seminar:
Seminar
 
Effective Java - Java best practice programming techniques, common pitfalls, and off-the-beaten-path language features
4 day seminar ( open enrollment and on-site)
 
  © Copyright 1995-2012 by Angelika Langer.  All Rights Reserved.    URL: < http://www.AngelikaLanger.com/Articles/EffectiveJava/28.Enums/28.Enums.html  last update: 4 Nov 2012