Enumerationen und Collections
EnumSet und EnumMap
- Es gibt spezielle Collection-Klassen für Enumerationen
EnumSet
EnumMap
EnumSet
dient der effizienten Speicherung von Enumerationen (interne Darstellung als Bit-Felder und nicht als Array)- Bieten zusätzliche Methoden, um Mengen von Konstanten zu verwalten (Differenzmengen etc.)
Da Enumerationen in Java durch vollwertige Objekte realisiert werden, könnte man natürlich zu deren Speicherung normale Collections verwenden. Diese Speicherung ist aber nicht sonderlich effizient, weil man pro Enum-Konstante ein Referenz von 4 oder 8 Byte benutzen muss. Deutlich effizienter ist die Ablage als Bit-Feld. Hier wird einfach für jede mögliche Konstante einer Enumeration ein Bit reserviert. Ist das Bit gesetzt, ist die Konstante im Set vorhanden, ist es nicht gesetzt, fehlt sie. Anstatt 8 Byte benötigt man dann für jede mögliche Konstante nur noch ein Bit. Unsere Enumeration Planet
mit 8 Planeten-Konstanten kann daher in einem einzigen Byte gespeichert werden, anstatt 64 Byte zu verbrauchen, wie es in einem normalen Set nötig wäre.
Zusätzlich zu der Speichereffizienz bieten die speziellen Klassen für Enumerationen noch Methoden an, um Mengenoperationen durchzuführen. Diese werden später noch vorgestellt.
Beispiel: Todesstern mit EnumSet
import java.util.EnumSet;
import static pr2.Planet.*;
public class Todesstern {
public void zerstoere(EnumSet<Planet> planeten) {
for (Planet planet : planeten) {
richteTodesstrahlAus(planet);
}
}
public static void main(String[] args) {
Todesstern ts = new Todesstern();
ts.zerstoere(EnumSet.of(MARS, VENUS, JUPITER));
}
...
}
Das Beispiel zeigt einen Todesstern, der in einem Durchlauf mehrere Planeten vernichten kann. Zu diesem Zweck wird der Methode zerstoere()
ein EnumSet
mit allen Planeten übergeben, die er vernichten soll.
Praktisch ist die Möglichkeit über die statische Methode of()
der Klasse EnumSet
die Konstanten direkt anzugeben, die eingefügt werden sollen. Ohne diese Methode müsste man deutlich umständlicher schreiben:
EnumSet<Planet> planeten = new EnumSet<>();
planeten.add(Planet.MARS);
planeten.add(Planet.VENUS);
planeten.add(Planet.JUPITER);
Beispiel: Hilfsmethoden auf EnumSet
EnumSet<Planet> planeten = EnumSet.allOf(Planet.class);
System.out.println("allOf: " + planeten);
EnumSet<Planet> leer = EnumSet.noneOf(Planet.class);
System.out.println("noneOf: " + leer);
EnumSet<Planet> dieAnderen = EnumSet.complementOf(EnumSet.of(Planet.ERDE));
System.out.println("complementOf: " + dieAnderen);
EnumSet<Planet> aeussere = EnumSet.range(Planet.MARS, Planet.NEPTUN);
System.out.println("range: " + aeussere);
allOf: [MERKUR, VENUS, ERDE, MARS, JUPITER, SATURN, URANUS, NEPTUN]
noneOf: []
complementOf: [MERKUR, VENUS, MARS, JUPITER, SATURN, URANUS, NEPTUN]
range: [MARS, JUPITER, SATURN, URANUS, NEPTUN]
Das Beispiel zeigt weitere nützliche Methoden des EnumSet
:
allOf
liefert ein Set, in dem alle Konstanten der Enumeration enthalten sindnoneOf
liefert ein leeres EnumSetcomplementOf
liefert ein Set mit allen Konstanten der Enumeration bis auf die als Parameter übergebenenrange
liefert ein Set mit allen Konstanten „zwischen“ den beiden Parametern
Beispiel: Ohne EnumSet (old school)
public class Auszeichnungen {
public static final int BOLD = 0x01;
public static final int ITALIC = 0x02;
public static final int UNDERLINE = 0x04;
public static final int STRIKEOUT = 0x08;
public static final int OUTLINED = 0x10;
}
Bevor es Enumerationen und die dazugehörigen Collection-Klassen gab, musste man sich eine effiziente Speicherung selbst bauen. Dazu verwendete man int
-Werte, die Zweierpotenzen waren.
Textprogramm t = new Textprogramm();
t.setAuszeichnung(BOLD | UNDERLINE | STRIKEOUT);
Der Verwender konnte dann die gewünschten Werte durch ein bitweises Oder (|
) verknüpfen. Hierdurch wurden die Bits gesetzt, die den Konstanten entsprachen.
public void setAuszeichnung(int auszeichnung) {
if ((auszeichnung & BOLD) > 0) {
System.out.println("Fett");
}
if ((auszeichnung & ITALIC) > 0) {
System.out.println("Kursiv");
}
if ((auszeichnung & UNDERLINE) > 0) {
System.out.println("Unterstrichen");
}
if ((auszeichnung & STRIKEOUT) > 0) {
System.out.println("Durchgestrichen");
}
if ((auszeichnung & OUTLINED) > 0) {
System.out.println("Umriss");
}
}
Durch Anwenden einer Bitmaske konnte dann die Methode teste, welche Bits in dem übergebenen int
-Wert gesetzt waren.
Beispiel: Mit EnumSet
public enum Auszeichnungen {
BOLD, ITALIC, UNDERLINE, STRIKEOUT, OUTLINED;
}
Textprogramm t = new Textprogramm();
t.setAuszeichnung(EnumSet.of(BOLD, UNDERLINE, STRIKEOUT));
public void setAuszeichnung(EnumSet<Auszeichnungen> auszeichnung) {
if (auszeichnung.contains(BOLD)) {
System.out.println("Fett");
}
if (auszeichnung.contains(ITALIC)) {
System.out.println("Kursiv");
}
if (auszeichnung.contains(UNDERLINE)) {
System.out.println("Unterstrichen");
}
if (auszeichnung.contains(STRIKEOUT)) {
System.out.println("Durchgestrichen");
}
if (auszeichnung.contains(OUTLINED)) {
System.out.println("Umriss");
}
}
Mit Enumerationen und EnumSet
fällt das Beispiel deutlich übersichtlicher und einfacher aus. Vor allem gibt es deutlich weniger Fehlerquellen.