CodeGym /Kursy /JAVA 25 SELF /EnumSet/EnumMap

EnumSet/EnumMap

JAVA 25 SELF
Poziom 28 , Lekcja 3
Dostępny

1. Wprowadzenie

W Javie istnieją specjalne kolekcje do pracy z wyliczeniami (enum): EnumSet i EnumMap. Wchodzą w skład standardowej biblioteki (java.util) i są przeznaczone do maksymalnie efektywnej pracy z typami enum.

Jak działają EnumSet i EnumMap pod spodem?

EnumSet działa jak maska bitowa. Wyobraź sobie zestaw przełączników, w którym każdy element wyliczenia zajmuje dokładnie jeden bit. Jeśli bit jest włączony — element jest w zbiorze, jeśli wyłączony — elementu nie ma. Wszystko jest przechowywane w tablicy liczb (long[]), a jeśli w Twoim enum jest mniej niż 64 wartości, cały zestaw mieści się w jednej jedynej liczbie!

EnumMap jest zbudowany jeszcze prościej: to tablica wartości, gdzie indeksem jest numer porządkowy (ordinal) elementu enum. Zamiast typowego HashMap<Enum, V> otrzymujesz bardzo kompaktową i szybką strukturę.

Co to daje? Operacje dodawania, usuwania i sprawdzania wykonywane są w O(1). Wykorzystywane jest minimum pamięci (zwłaszcza w porównaniu z HashSet i HashMap). Iteracja elementów zawsze przebiega w kolejności deklaracji w enum, co czyni wynik przewidywalnym.

Przykład: jak to wygląda

enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }

EnumSet<Day> weekend = EnumSet.of(Day.SAT, Day.SUN);
System.out.println(weekend); // [SAT, SUN]

Z zewnątrz to zwykły zbiór. Ale w środku — liczba, w której ustawione są dwa bity: jeden dla SAT, drugi dla SUN. Dodasz FRI — włączy się kolejny bit. Bez tabeli mieszającej i zbędnych obiektów.

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}

Tutaj klucze (Day) zamieniają się na wewnętrzne indeksy tablicy, dlatego dostęp działa tak szybko, jak odwołanie do elementu tablicy.

2. Przykłady użycia: flagi, tabele, automaty skończone

EnumSet: idealny do flag i zbiorów stanów

  • Flagi: przechowywanie zestawów „włączone/wyłączone” dla ograniczonej liczby opcji.
  • Zbiory wartości enum: dni tygodnia, uprawnienia użytkownika, stany zadania.
  • Automaty skończone (FSM): wygodnie przechowywać dozwolone przejścia w EnumSet.

Przykład: flagi dostępu

enum Permission { READ, WRITE, EXECUTE }

EnumSet<Permission> perms = EnumSet.of(Permission.READ, Permission.WRITE);
if (perms.contains(Permission.WRITE)) {
    // Pisanie dozwolone
}

Przykład: wszystkie wartości poza niektórymi

EnumSet<Day> workdays = EnumSet.complementOf(EnumSet.of(Day.SAT, Day.SUN));
System.out.println(workdays); // [MON, TUE, WED, THU, FRI]

EnumMap: idealny do tablic po kluczach enum

  • Tabele odwzorowań: kluczami są wartości enum, wartościami — dowolne obiekty.
  • Szybki dostęp: szybszy i bardziej kompaktowy niż HashMap<Enum, V>.

Przykład: ceny według dni tygodnia

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

Przykład: automat skończony

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. Pułapki: zmiany w enum i serializacja

EnumSet i EnumMap zależą od składu i kolejności wartości Twojego enum. Jeśli dodasz nowy element, usuniesz stary lub zmienisz ich kolejność, zapisane lub zserializowane kolekcje mogą zachowywać się niepoprawnie.

Serializacja

  • EnumSet i EnumMap są serializowalne, ale przy zmianie enum między serializacją a deserializacją mogą wystąpić błędy/utrata danych.
  • Usunięcie wartości z enum po serializacji niemal na pewno doprowadzi do wyjątku podczas odczytu.

Najlepsza praktyka:

  • Nie serializuj EnumSet/EnumMap, jeśli nie masz pewności, że enum jest stabilny.
  • Do długotrwałego przechowywania użyj na przykład listy tekstowych reprezentacji wartości.

4. Przydatne szczegóły

Porównanie EnumSet/EnumMap i zwykłych kolekcji

Kolekcja Klucz/element Wewnętrzna implementacja Wydajność Pamięć Null
EnumSet
enum
maska bitowa O(1) bardzo mało nie można
HashSet<Enum>
enum
tablica mieszająca O(1) więcej można
EnumMap
enum
tablica indeksowana przez ordinal O(1) bardzo mało nie można
HashMap<Enum, V>
enum
tablica mieszająca O(1) więcej można

Najlepsze praktyki

  • Używaj EnumSet do zbiorów wartości (of, noneOf, allOf, complementOf).
  • Używaj EnumMap do tabel asocjacyjnych, gdzie kluczem jest enum.
  • Unikaj „gigantycznych” wyliczeń — setki wartości pogarszają kompaktowość.
  • Nie serializuj, jeśli enum może się zmieniać.
  • Nie używaj null jako klucza ani wartości.

5. Praktyka: jak używać EnumSet i EnumMap w aplikacji

Przykład: przechowywanie ról użytkownika

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);
    }
}

Przykład: tabela przejść stanów

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. Typowe błędy przy pracy z EnumSet/EnumMap

Błąd nr 1: Użycie EnumSet/EnumMap z typem innym niż enum.
Te kolekcje działają wyłącznie z typami, które są enum.

// EnumSet<String> set = EnumSet.of("A", "B"); // Błąd kompilacji!

Błąd nr 2: Używanie EnumSet/EnumMap dla bardzo dużych enum.
Wyliczenia liczące setki wartości pogarszają kompaktowość. Mimo to wciąż często wypadają lepiej pamięciowo niż HashSet/HashMap dla tych samych danych.

Błąd nr 3: Zmiana enum po serializacji.
Dodanie/usunięcie/zmiana kolejności wartości po serializacji prowadzi do błędów przy odczycie lub do utraty danych.

Błąd nr 4: Używanie EnumSet/EnumMap z null.
Ani elementy EnumSet, ani klucze/wartości EnumMap nie mogą być null.

EnumSet<Day> days = EnumSet.of(null); // NullPointerException!
EnumMap<Day, String> map = new EnumMap<>(Day.class);
map.put(null, "test"); // NullPointerException!

Błąd nr 5: Oczekiwanie, że EnumSet to „zwykły” Set.
EnumSet przechowuje tylko wartości swojego enum; nie możesz dodać tam dowolnego obiektu spoza wyliczenia.

Błąd nr 6: Używanie EnumSet/EnumMap dla „zmiennych” enum.
Jeśli wyliczenia są generowane lub podmieniane w czasie działania (dynamiczne ładowanie/refleksja), te struktury nie będą działać poprawnie.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION