1. Einleitung
In Java gibt es spezielle Collections für die Arbeit mit Aufzählungen (enum): EnumSet und EnumMap. Sie sind Teil der Standardbibliothek (java.util) und für maximale Effizienz im Umgang mit enum-Typen gedacht.
Wie funktionieren EnumSet und EnumMap intern?
EnumSet arbeitet wie eine Bitmaske. Stellen Sie sich eine Reihe von Schaltern vor, bei der jedes Element der Aufzählung genau ein Bit belegt. Ist das Bit gesetzt – befindet sich das Element in der Menge; ist es gelöscht – fehlt es. Alles wird in einem Zahlenarray (long[]) gespeichert, und wenn Ihr enum weniger als 64 Werte hat, passt die gesamte Menge in eine einzige Zahl!
EnumMap ist noch einfacher aufgebaut: Es ist ein Wertearray, dessen Index die Ordnungszahl (ordinal) eines enum-Elements ist. Statt einer üblichen HashMap<Enum, V> erhalten Sie eine sehr kompakte und schnelle Struktur.
Was bringt das? Einfüge-, Lösch- und Prüfoperationen laufen in O(1). Es wird minimaler Speicher verwendet (insbesondere im Vergleich zu HashSet und HashMap). Die Iteration erfolgt stets in der Deklarationsreihenfolge im enum, was die Ergebnisse vorhersehbar macht.
Beispiel: so sieht es aus
enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }
EnumSet<Day> weekend = EnumSet.of(Day.SAT, Day.SUN);
System.out.println(weekend); // [SAT, SUN]
Nach außen ist das eine gewöhnliche Menge. Intern jedoch eine Zahl, in der zwei Bits gesetzt sind: eines für SAT, eines für SUN. Fügen Sie FRI hinzu – wird ein weiteres Bit gesetzt. Keine Hash-Tabelle und keine überflüssigen Objekte.
EnumMap<Day, String> schedule = new EnumMap<>(Day.class);
schedule.put(Day.MON, "Gym");
schedule.put(Day.FRI, "Party");
System.out.println(schedule); // {MON=Gym, FRI=Party}
Hier werden die Schlüssel (Day) in interne Array-Indizes umgewandelt, daher ist der Zugriff so schnell wie ein Arrayzugriff.
2. Anwendungsfälle: Flags, Tabellen, endliche Automaten
EnumSet: ideal für Flags und Zustandsmengen
- Flags: Speichern von „ein/aus“ für eine begrenzte Anzahl von Optionen.
- Mengen von enum-Werten: Wochentage, Benutzerberechtigungen, Aufgabenstatus.
- Endliche Automaten (FSM): zulässige Übergänge lassen sich bequem in EnumSet speichern.
Beispiel: Zugriffs-Flags
enum Permission { READ, WRITE, EXECUTE }
EnumSet<Permission> perms = EnumSet.of(Permission.READ, Permission.WRITE);
if (perms.contains(Permission.WRITE)) {
// Schreiben erlaubt
}
Beispiel: alle Werte außer einigen
EnumSet<Day> workdays = EnumSet.complementOf(EnumSet.of(Day.SAT, Day.SUN));
System.out.println(workdays); // [MON, TUE, WED, THU, FRI]
EnumMap: ideal für Tabellen mit enum-Schlüsseln
- Zuordnungstabellen: Schlüssel sind Werte des enum, Werte – beliebige Objekte.
- Schneller Zugriff: schneller und kompakter als HashMap<Enum, V>.
Beispiel: Preise pro Wochentag
EnumMap<Day, Integer> prices = new EnumMap<>(Day.class);
prices.put(Day.MON, 100);
prices.put(Day.SAT, 200);
System.out.println(prices.get(Day.SAT)); // 200
Beispiel: endlicher Automat
enum State { START, RUNNING, STOPPED }
EnumMap<State, EnumSet<State>> transitions = new EnumMap<>(State.class);
transitions.put(State.START, EnumSet.of(State.RUNNING));
transitions.put(State.RUNNING, EnumSet.of(State.STOPPED));
transitions.put(State.STOPPED, EnumSet.noneOf(State.class));
3. Fallstricke: Änderungen am enum und Serialisierung
EnumSet und EnumMap hängen von Zusammensetzung und Reihenfolge der Werte Ihres enum ab. Wenn Sie ein neues Element hinzufügen, ein altes entfernen oder die Reihenfolge ändern, können gespeicherte oder serialisierte Collections sich fehlerhaft verhalten.
Serialisierung
- EnumSet und EnumMap sind serialisierbar, aber wenn sich das enum zwischen Serialisierung und Deserialisierung ändert, sind Fehler/Datenverlust möglich.
- Das Entfernen eines Werts aus dem enum nach der Serialisierung führt beim Einlesen nahezu sicher zu einer Ausnahme.
Best Practices:
- Serialisieren Sie EnumSet/EnumMap nicht, wenn Sie sich nicht sicher sind, dass das enum stabil ist.
- Für die langfristige Speicherung verwenden Sie z. B. eine Liste der String-Repräsentationen der Werte.
4. Nützliche Feinheiten
Vergleich von EnumSet/EnumMap mit gewöhnlichen Sammlungen
| Sammlung | Schlüssel/Element | Interner Aufbau | Leistung | Speicher | Null |
|---|---|---|---|---|---|
|
|
Bitmaske | O(1) | sehr wenig | nicht erlaubt |
|
|
Hash-Tabelle | O(1) | mehr | erlaubt |
|
|
Array nach ordinal | O(1) | sehr wenig | nicht erlaubt |
|
|
Hash-Tabelle | O(1) | mehr | erlaubt |
Best Practices
- Verwenden Sie EnumSet für Wertemengen (of, noneOf, allOf, complementOf).
- Verwenden Sie EnumMap für Zuordnungstabellen, bei denen der Schlüssel – enum – ist.
- Vermeiden Sie „riesige“ Aufzählungen – Listen mit Hunderten von Werten verschlechtern die Kompaktheit.
- Nicht serialisieren, wenn sich das enum ändern kann.
- Verwenden Sie null weder als Schlüssel noch als Wert.
5. Praxis: wie man EnumSet und EnumMap in einer Anwendung verwendet
Beispiel: Speichern von Benutzerrollen
enum Role { USER, ADMIN, MODERATOR }
class User {
private EnumSet<Role> roles = EnumSet.noneOf(Role.class);
public void addRole(Role role) {
roles.add(role);
}
public boolean isAdmin() {
return roles.contains(Role.ADMIN);
}
}
Beispiel: Übergangstabelle von Zuständen
enum State { NEW, IN_PROGRESS, DONE }
EnumMap<State, EnumSet<State>> transitions = new EnumMap<>(State.class);
transitions.put(State.NEW, EnumSet.of(State.IN_PROGRESS));
transitions.put(State.IN_PROGRESS, EnumSet.of(State.DONE));
transitions.put(State.DONE, EnumSet.noneOf(State.class));
6. Typische Fehler im Umgang mit EnumSet/EnumMap
Fehler Nr. 1: Verwendung von EnumSet/EnumMap mit einem Nicht-enum-Typ.
Diese Collections funktionieren nur mit Typen, die ein enum sind.
// EnumSet<String> set = EnumSet.of("A", "B"); // Kompilierfehler!
Fehler Nr. 2: Verwendung von EnumSet/EnumMap für sehr große enums.
Aufzählungen mit Hunderten von Werten verschlechtern die Kompaktheit. Dennoch ist das speicherseitig in der Regel immer noch besser als HashSet/HashMap für dieselben Daten.
Fehler Nr. 3: Änderungen am enum nach der Serialisierung.
Das Hinzufügen/Entfernen/Umordnen von Werten nach der Serialisierung führt beim Lesen zu Fehlern oder zu Datenverlust.
Fehler Nr. 4: Verwendung von EnumSet/EnumMap mit null.
Weder die Elemente von EnumSet noch die Schlüssel/Werte von EnumMap dürfen null sein.
EnumSet<Day> days = EnumSet.of(null); // NullPointerException!
EnumMap<Day, String> map = new EnumMap<>(Day.class);
map.put(null, "test"); // NullPointerException!
Fehler Nr. 5: Die Erwartung, dass EnumSet ein „gewöhnliches“ Set ist.
EnumSet speichert nur Werte seines eigenen enum; Sie können dort kein beliebiges Objekt außerhalb der Aufzählung hinzufügen.
Fehler Nr. 6: Verwendung von EnumSet/EnumMap für „veränderliche“ enums.
Wenn Aufzählungen zur Laufzeit generiert oder ausgetauscht werden (dynamisches Laden/Reflection), funktionieren diese Strukturen nicht korrekt.
GO TO FULL VERSION