Enum
🚧 | Diese Seite befindet sich in Bearbeitung | 🚧 |
🤓 | Diese Seite ist eine Bewertungsrichtlinie, die ab Blatt 2 annotiert und ab Blatt 3 abgezogen wird. | 🤓 |
Beschreibung
Motivation
Wenn wir Probleme und Konzepte bei der Entwicklung von Software modellieren, trifft man öfter Konzepte an, die abgeschlossene Mengen sind. Beispiele hierfür sind Monate, Farben, Wochentage, Spielmodi, (Benutzer-) Rollen, Kategorien, Zustände, etc.
Wir wollen für eine abgeschlossene Menge an Daten, zum Beispiel Wochentagen, einen zentralen Datentyp haben. Dieser soll sicherstellen, das Werte dieses Typs nur Werte aus der Menge sein können (z.B. nur Montag bis Sonntag). Diesen Datentyp soll leicht erweiterbar sein (z.B. wenn eine neue Benutzerrolle eingeführt wird). Das würde dafür sorgen, dass wir keine Konstantenklasse benötigen und auch nicht sämtliche Werte über mehrere Klassen verteilen müssen. Zusätzlich wollen wir ggf. über diese definierten Werten bestimmte Operationen durchführen.
Dafür sollten IMMER Enums verwendet werden.
Beispiel:
public enum Seasons {
WINTER,
SPRING,
SUMMER,
AUTUMN;
}
Einführung
Die Oracle Java Dokumentation beschreibt Enums wie folgt: "An enum declaration specifies a new enum class, a restricted kind of class that defines a small set of named class instances."
Was heißt das?
- "An enum declaration specifies a new enum class ..."
- Ein Enum ist ein eigener "Klassentyp", und muss deshalb nicht in eine sogenannte "Wrapper Klasse" eingebunden werden. Sie steht alleine.
- "... a restricted kind of class ..."
- Ein Enum hat eingeschränktere Funktionalität als herkömmliche Klassen, haben dafür allerdings eine Menge hilfreicher, vorgefertigter Methoden. Es kommt selten vor, dass selbst Methoden implementiert werden müssen.
- "... that defines a small set of named class instances"
- Die Werte eines Enum, also das, was wir in späterer Konvention CAPS schreiben, sind keine Attribute wie wir das jetzt erstmal erwarten würden, sondern Instanzen. Das heißt insbesondere, dass die Instanzen der Klasse innerhalb des Enums gespeichert wird, was erstmal gewöhnungsbedürftig sein könnte.
Schauen wir uns den Satz über die eingeschränkte Funktionalität noch einmal genau an:
Eingeschränkte Funktionalität
In den Java Specs zu Enums steht folgendes:
- "An enum class is implicitly final if its declaration contains no enum constants that have a class body"
- Eine Enum Klasse ist also immer implizit final, wenn die Instanzen keine weiteren Attribute haben.
- "An enum class E is implicity sealed if its declaration contains at least one enum constant that has a class body. [...]"
- Eine Enum Klasse ist immer genau dann sealed, wenn die Instanzen weitere Attribute besitzen.
- "A nested enum class is implicitly static. That is, every member enum class and local enum class is static."
- Eine Enum Klasse, die innerhalb einer weiteren Klasse ist, ist implizit statisch und alle Instanzen innerhalb dieses Enums sind auch statisch.
Zusammengefast: Einmal deklariert, können und sollen die Instanzen des Enums selbst nicht mehr verändert werden.
Zusätzlich gilt:
"It is a compile-time error if the same keyword appears more than once [...] or if an enum declaration has more than one of the access modifiers public, protected and private".
Es dürfen also nicht mehrere Instanzen denselben Namen haben und alle Instanzen müssen dieselbe Sichtbarkeit haben (in der Regel geben wir ihnen keine explizite Sichtbarkeit, siehe Beispiel). Ist dem nicht so, ist das Program nicht kompilierbar.
Enum-Instanten können, ähnlich zu Klassen, auch selbst Attribute mit sich tragen:
Enum-Instanzen mit Attributen
Irgendwie müssen wir einer Instanz mitteilen, dass sie bitte noch Attribute haben sollen. Wir nehmen dafür einfach mal einen "normalen" Klassen-Konstruktor. Eine Instanz eines Enum E erhält dann einen Konstruktor in seine Klasse E(). In diese Klammern übergeben wir jetzt mal zwei generische Attribute:
E(Attribut1 wert1, Attribut2 wert2) {
this.wert1 = wert1;
this.wert2 = wert2;
}
Irgendwo müssen wir diese Attribute noch abspeichern. Dafür errinnern wir uns an die eingeschränkte Funktionalität und setzen diese Attribute als final. Wir ergänzen also folgendes:
private final Attribut1 wert1;
private final Attribut2 wert2;
Auch können wir ganz normal, wie bekannt Methoden definieren. Dafür gilt aber auch die eingeschränkte Funktionalität. Das heißt, Attribute dürfen nicht verändert werden (und können auch nicht, da sie final sind).
Beispiel
Was heißt das jetzt konkret? Machen wir mal ein Beispiel für ein simples Himmelsrichtungen-Enum:
public enum Direction {
NORTH,
EAST,
SOUTH,
WEST;
}
Wir haben also alles abgedeckt, was wir gerade besprochen haben:
Es ist ein eigener Typ, nämlich Direction, hat eingeschränkte Funktionalität (in der Regel nur Vergleichbarkeit) und es gibt nur wenige, endlich viele Instanzen. Hier 4.
Ein Beispiel für ein Enum mit Attributen und Methoden aus der Java Dokumentation:
public enum Planet {
MERCURY (3.303e+23, 2.4397e6),
VENUS (4.869e+24, 6.0518e6),
EARTH (5.976e+24, 6.37814e6),
MARS (6.421e+23, 3.3972e6),
JUPITER (1.9e+27, 7.1492e7),
SATURN (5.688e+26, 6.0268e7),
URANUS (8.686e+25, 2.5559e7),
NEPTUNE (1.024e+26, 2.4746e7);
private final double mass; // in kilograms
private final double radius; // in meters
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
private double mass() { return mass; }
private double radius() { return radius; }
// universal gravitational constant (m3 kg-1 s-2)
private static final double G = 6.67300E-11;
double surfaceGravity() {
return G * mass / (radius * radius);
}
double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();
}
}
Konventionen
Letztlich ist es wichtig über die Konventionen zu sprechen:
- Die Werte/Instanzen eines Enums werden in UPPER_SNAKE_CASE geschrieben, da sie dem Konzept einer statischen Konstante folgen.
- Enums in einer Klasse (verschachtelt/nested) sollten, wie alle innere Klassen, private sein. Ist es nicht möglich das Enum private zu machen, sollte es in einer eigenen Datei stehen.
- Enums müssen zustandslos sein, oder zumindest nicht von außen veränderbar. Das geht aus der vorherigen Aufbröselung der Java Documentation heraus und ist demnach nur ergänzend hier notiert.
- Es gibt zwei Methoden um den Namen der Instanz zu erhalten,
name()
undtoString()
. Nach Java Dokumentation solltetoString()
benutzt werden, da diese Methode einen besser lesbaren Namen zurückgibt.
Fallunterscheidung mit Enums statt Vererbung
Es ist wichtig darüber zu reden, dass Klassen bzw. deren Objekte mit Enums passend markiert werden können und dementsprechend über einen switch-Statement bestimmte Verhaltensweisen bestimmt werden können. Dafür sind Enums nicht da.
Eine bessere Lösung dafür wäre es, die Modellierung durch Vererbung zu gestalten und entsprechend Methoden zu definieren/vererben. Das wird auch in einem Beispielpaar gleich gezeigt.
Negativbeispiel
public enum Alphabet {
a,
b,
/ [...]
y,
z;
}
Für ein Alphabet würden wir kein Enum verwenden. Die Menge ist zwar endlich, aber das Enum so ist nicht zu gebrauchen. Es würde sich eher eignen, die Buchstaben durch ihren ASCII Code zu identifizieren.
Hier ein Beispiel, wie Fallunterscheidung nicht umzusetzen ist:
public class Customer {
private double balance;
private CustomerType type;
public Customer(double balance,
CustomerType type) {
this.balance = balance;
this.type = type;
}
double payEntryFee() {
double fee;
switch(type) {
case CHILD : fee = 5.00; break;
case ADULT : fee = 10; break;
case SENIOR : fee = 8; break;
}
balance -= fee;
return fee;
}
}
Positivbeispiel
public enum Day {
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY;
}
Hier eine verbesserte Version, indem die Fallunterscheidung durch Vererbung gelöst wurde:
public abstract class Customer {
private double balance;
public Customer(double balance) {
this.balance = balance;
}
double payEntryFee() {
double fee = getEntryFee();
balance -= fee;
return fee;
}
abstract double getEntryFee();
}
public class Child extends Customer {
public Child(double balance) {
super(balance);
}
double getEntryFee() {
return 7.5;
}
}
Im folgenden gibt es ein paar Links, wo etwas mehr zu Methoden und Enums zu finden ist: