Java Tutorial #9 - "Aufzählungen mit enum"

Autor: 'Kriz'

Wichtige Hinweise!!!

Die Grundlagen über die Java-Programmierung ist nicht Bestandteil der Tutorialreihe! Zum Lernen von Java empfehle ich das hervorragende Online-Tutorial von Guido Krüger, welches man sich kostenlos von www.javabuch.de runterladen kann.Außerdem verweise ich auch auf die ebenfalls umfangreiche und sehr gute Online-Tutorialreihe von Galileo Computing, das man sich ebenfalls kostenlos hier herunterladen kann.

Dieses Tutorial ist ausschließlich ab Java 5 (JDK 1.5.x) gedacht!


In diesem Tutorial gehe ich auf eine der neuen Sprachbestandteile von Java 5 ein, nämlich die Aufzählung (enum). Dieses Feature ist bei C/C++ seit jeher Bestandteil der Sprache und wurde nach längerer Zeit nun auch endlich in Java eingeführt. Um es aber gleich vorweg zu nehmen: Enums in Java kann man nur funktionell mit enums in C/C++ vergleichen! Syntaktisch und semantisch betrachtet unterscheiden sich beide Komponenten erheblich voneinander. Warum sehen wir später.

Auf jeden Fall möchte ich hier weniger die Eigenschaften einer enum in Java beschreiben, als vielmehr die Möglichkeiten (und Beschränkungen) aufzeigen. Eine enum in Java ist nämlich weitaus komplizierter zu handhaben als ihr Pedant in C/C++.

Zuerst einmal wieder etwas graue Theorie:

Der aufzählende Datentyp enum tut nichts weiter als Ganzzahlen einen Aliasnamen zu verpassen sowie eine aufsteigende Ordnung zu bilden. Diese geordneten Ganzzahlen haben dann syntaktisch betrachtet keinerlei Bezug mehr zu den primitiven Ganzzahlen wie byte, short, int oder long. Auch der Bezug zu den Wrapperklassen Byte, Short, Integer oder Long geht verloren. Ein enum kann man am besten mit einem anschaulichen Beispiel erklären und die Woche als solche ist dazu bestens geeignet als enum abgebildet zu werden:

Wie wir wissen hat eine Woche 7 Tage. Ob die Woche nun ab dem Sonntag oder erst ab dem Montag an gezählt wird, spielt hierbei erstmal keine Rolle, obwohl ich letztere Variante bevorzuge. Jeder Wochentag hat eine Bezeichnung: Montag, Dienstag, Mittwoch, Donnerstag, Freitag, Samstag und Sonntag. Die Reihenfolge ist immer die gleiche, weshalb die Wochentage aufsteigend geordnet sind. So kann man nun jedem Wochentag eine Zahl im Sinne der Programmierung zuordnen, sprich ab dem Index 0 einen Wert zuordnen:

Montag = 0
Dienstag = 1
Mittwoch = 2
Donnerstag = 3
Freitag = 4
Samstag = 5
Sonntag = 6

Da sich die Reihenfolge wie gesagt niemals ändert, können wir stets von der Ordnung der Wochentage ausgehen. Aus dieser Ordnung heraus können wir nun in Java ein enum bilden:

public enum Wochentag
{
    Montag, 
    Dienstag, 
    Mittwoch, 
    Donnerstag, 
    Freitag, 
    Samstag, 
    Sonntag;
}

Die Aufzählung beginnt standardmäßig immer bei 0 und inkrementiert sich mit jedem neuen Eintrag in der enum. Der letzte Eintrag muß mit einem Semikolon abgeschlossen werden (im Gegensatz zu C/C++, wo der Klammernblock mit einem Semikolon abgeschlossen werden muß).

Das Schlüsselwort enum täuscht allerdings über eine Tatsache hinweg: Ein enum in Java ist nicht ein Datentyp wie in C/C++, sondern eine Art Wrapper. Der Java Compiler erkennt enums und wandelt sie intern um in spezielle Klassen. Das entspricht der objektorientierten Philosophie in Java und ermöglicht es (ähnlich wie mit Arrays oder Strings) Methoden auf enums anzuwenden. In der Tat gibt es einige Methoden, die man auf enum-Variablen anwenden kann (und auch muß).

Frei definierbare Ordnungswerte:

Bevor ich nun dieses Kapitel anschneide, möchte ich die oben definierte enum einmal exemplarisch in C++ darstellen:

enum Wochentag
{
    Montag, 
    Dienstag, 
    Mittwoch, 
    Donnerstag, 
    Freitag, 
    Samstag, 
    Sonntag
};

So gesehen unterscheidet sich die C++ Variante inhaltlich nicht von der Java Variante oben. Man kann aber in C/C++ den Aufzählungsbezeichnern direkt Werte zuweisen und so die Ordnung selbst in die Hand nehmen. Wenn man also anstatt dem Montag lieber den Sonntag an den Anfang der Aufzählung stellen möchte, so vergibt man in C/C++ einfach die passenden Ordnungswerte direkt an die Einträge:

enum Wochentag
{
    Montag = 1, 
    Dienstag = 2, 
    Mittwoch = 3, 
    Donnerstag = 4, 
    Freitag = 5, 
    Samstag = 6, 
    Sonntag = 0  // Steht ordnungsmäßig nun vor dem Montag
};

In Java dagegen würde eine ähnliche Konstruktion zu einem Compilerfehler führen:

public enum Wochentag
{
    // Fette Fehler in Java
    Montag = 1, 
    Dienstag = 2, 
    Mittwoch = 3, 
    Donnerstag = 4, 
    Freitag = 5, 
    Samstag = 6, 
    Sonntag = 0;
}

Das liegt daran, daß die Einträge in einer enum von Java als Objekte aufgefaßt werden und der = Operator nicht für diese Objekte überladen ist (Java erlaubt keine Operatorenüberladung). Außerdem wird hier entgegen der strengen Typprüfung von Java versucht, einen Primitivwert (nämlich eine Ganzzahl vom Typ int) an ein (Klassen)Objekt zu übertragen. Viele Javaprogrammierer haben sich sofort über diese (logisch einfache) Restriktion bitterböse beschwert und auf die einfachere Schreibweise in C/C++ verwiesen. Allerdings haben sie nicht daran gedacht, daß enums wie gesagt verdeckte Klassen sind und nur der Form halber so implementiert werden wie oben. Innerhalb von Java sieht die Sache anders aus und so möchte ich auf die Methoden der enum-Klassen zu sprechen kommen.

Man kann wie oben im C++ Beispiel auch in Java den Einträgen einer enum sofort Werte zuweisen. Dazu muß man aber stets im Hinterkopf behalten, daß Java enums Klassen sind. Diesen Umstand macht man sich zunutze, indem man einfach die abgeleitete Klasse (denn nichts anderes sind enums in Java) zu erweitern. Von Haus aus vergibt der Java Compiler jedem Eintrag aufsteigend seine Ordnungszahl. Möchte man nun dieses Verhalten brechen, so muß man der enum als Klasse betrachtet ein passendes Klassenelement verabreichen. Dazu wählen wir den Primitivtyp int, der als private deklariert in die enum implementiert wird:

public enum Wochentag
{
    Montag, 
    Dienstag, 
    Mittwoch, 
    Donnerstag, 
    Freitag, 
    Samstag, 
    Sonntag;
 
    private int wert;
}

Da wie gesagt jeder Eintrag ein Objekt der enum selbst ist, besitzt also jeder Eintrag auch sein eigenes Element wert, auf dem nur das Objekt selbst Zugriff hat. Stellt sich nur noch die Frage, wie übergebe ich jedem Objekt seinen Wert? Dazu betrachten wir uns mal den Eintrag Montag genauer:

Man kann den Eintrag Montag auch als typische Java-Alloziierung mit leerer Parameterliste betrachten:

Wochentag Montag = new Wochentag();

Aha! Dann müssen wir also nur den Ordnungswert als Parameter mitgeben. Genau!

Wochentag Montag = new Wochentag(int ordnungswert);

Dazu müssen wir aber der enum einen passenden Konstruktor mitgeben, der einen int Wert akzeptiert. Dieser Konstruktor darf dabei nicht public sein, denn das Anlegen der Einträge in einer enum geschieht statisch auf private Ebene, da man ja nach der Definition der enum die Ordnungswerte nicht mehr ändern darf:

public enum Wochentag
{
    Montag(1), 
    Dienstag(2), 
    Mittwoch(3), 
    Donnerstag(4), 
    Freitag(5), 
    Samstag(6), 
    Sonntag(0);  // Jetzt klappt das auch mit Java!
 
    private int wert;
 
    private Wochentag(int ordnungswert)
    {
        wert = ordnungswert;
    }
}

Soweit sogut. Nur wie kommt man jetzt typsicher an die Ordnungswerte dran? Man kann ein enum Objekt nicht einfach einer int Variablen zuweisen, das würde gegen die Typdefinition verstoßen. Sowas wie hier geht nicht:

int ordnungswert = Wochentag.Freitag; // Fehler!!!

Jetzt kommen die Methoden einer enum Klasse ins Spiel. Um an den Ordnungswert dranzukommen, besitzt jede enum eine Methode namens ordinal(). Diese gibt die sogenannte Ordinalzahl einer Aufzählung an den Aufrufer zurück:

int ordnungswert = Wochentag.Freitag.ordinal(); // Jetzt geht's

Einen dicken Haken hat die Sache allerdings noch! Man kann mit ordinal() nicht in switch Konstrukten arbeiten, da die Ordinalzahl eines enum Objekts zur Laufzeit ermittelt wird und daher keine konstante, zur Kompilierzeit bekannte Größe darstellt. Sowas hier (was in C/C++ ohne Probleme funktioniert) geht in Java nicht:

int ordnungswert = 1;
 
switch(ordnungswert)
{
case Wochentag.Montag.ordinal(): // Fehler!!!
    // irgendwas...
    break;
default:
    break;
}

Leider gibt es hierfür keine Lösung! Das einzige was hier funktioniert ist das direkte Switchen:

Wochentag tag = Wochentag.Montag;
 
switch(tag)
{
case Montag:
    // Es ist Montag o_O
    break;
default:
    break;
}

Hier greift der Compiler direkt auf die Objekte der enum zu und kann diese vergleichen.

Wir fassen zusammen:

Enums in Java unterscheiden sich hinsichtlich von enums in C/C++. Obwohl die Funktion dieselbe ist, ist der Aufbau grundverschieden. Man muß mehr Aufwand betreiben, hat dafür aber alle Möglichkeiten einer klassenbasierten Objektinstanz (inkl. Vererbung und Erweiterung).

Durch die Einführung der enums in Java muß man jetzt nicht mehr gezwungenermaßen ein Interface als Konstantencontainer mißbrauchen:

public interface Konstanten
{
    public int A = 1,
               B = 500,
               C = 12345;
}
 
// wird nun elegant zu
 
public enum Konstanten
{
    A(1),
    B(500),
    C(12345);
 
    private int wert;
 
    private Konstanten(int wert)
    {
        this.wert = wert;
    }
}

Anmerkung

Dieses Tutorial stammt aus der ehemaligen Sammlung des resourcecode.de und konnte dank der freundlichen Zustimmung des Autors in das thewall-Wiki übertragen werden.

Die Verwendung aller Dokumente einschließlich der Abbildungen ausschließlich zu nichtkommerziellen Zwecken. Verbreitung des Dokuments auf Speichermedien, (insbesondere auf CD-ROMs als Beilage zu Zeitschriften und Magazinen oder sog. "Mission-Packs" etc.) ist untersagt.
 
coding/java/java_tut_9.txt · Zuletzt geändert: 2008/11/03 18:47 von TomMe