JavaRush /Курси /JAVA 25 SELF /EnumSet/EnumMap

EnumSet/EnumMap

JAVA 25 SELF
Рівень 28 , Лекція 3
Відкрита

1. Вступ

У Java є спеціальні колекції для роботи з перерахуваннями (enum): EnumSet і EnumMap. Вони входять до стандартної бібліотеки (java.util) і призначені для максимально ефективної роботи з типами enum.

Як влаштовано EnumSet і EnumMap усередині?

EnumSet працює як бітова маска. Уявіть набір прапорців, де кожен елемент перерахування займає рівно один біт. Якщо біт увімкнено — елемент є в множині; якщо вимкнено — його немає. Усе зберігається в масиві чисел (long[]), і якщо ваш enum має менше ніж 64 значення, то весь набір уміститься в одному-єдиному числі!

EnumMap влаштовано ще простіше: це масив значень, де індексом слугує порядковий номер (ordinal) елемента enum. Замість звичного HashMap<Enum, V> ви отримуєте дуже компактну й швидку структуру.

Що це дає? Операції додавання, видалення та перевірки виконуються за O(1). Використовується мінімум памʼяті (особливо порівняно з HashSet і HashMap). Ітерування елементів завжди відбувається у порядку оголошення в enum, що робить результат передбачуваним.

Приклад: як це виглядає

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

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

Зовні це звичайна множина. А всередині — число, у якого виставлено два біти: один для SAT, інший для SUN. Додасте FRI — увімкнеться ще один біт. Жодної хеш-таблиці та зайвих обʼєктів.

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}

Тут ключі (Day) перетворюються на внутрішні індекси масиву, тому доступ працює так само швидко, як звернення до елемента масиву.

2. Випадки використання: прапорці, таблиці, скінченні автомати

EnumSet: ідеальний для прапорців і наборів станів

  • Прапорці: зберігання наборів «увімкнено/вимкнено» для обмеженої кількості параметрів.
  • Множина значень enum: дні тижня, дозволи користувача, стани завдання.
  • Скінченні автомати (FSM): зручно зберігати допустимі переходи в EnumSet.

Приклад: прапорці доступу

enum Permission { READ, WRITE, EXECUTE }

EnumSet<Permission> perms = EnumSet.of(Permission.READ, Permission.WRITE);
if (perms.contains(Permission.WRITE)) {
    // Дозволено записувати
}

Приклад: усі значення, окрім деяких

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

EnumMap: ідеальний для таблиць за enum-ключами

  • Таблиці відповідностей: ключами є значення enum, а значеннями — будь-які обʼєкти.
  • Швидкий доступ: швидше й компактніше, ніж HashMap<Enum, V>.

Приклад: ціни за днями тижня

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

Приклад: скінченний автомат

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. Підводні камені: зміна enum і серіалізація

EnumSet і EnumMap залежать від складу та порядку значень вашого enum. Якщо ви додасте новий елемент, видалите старий або поміняєте їх місцями, збережені чи серіалізовані колекції можуть поводитися некоректно.

Серіалізація

  • EnumSet і EnumMap підтримують серіалізацію, але у разі зміни enum між серіалізацією та десеріалізацією можливі помилки або втрата даних.
  • Видалення значення з enum після серіалізації майже гарантовано призведе до винятку під час читання.

Найкращі практики:

  • Не серіалізуйте EnumSet/EnumMap, якщо не впевнені, що enum стабільний.
  • Для тривалого зберігання використовуйте, наприклад, список рядкових подань значень.

4. Корисні нюанси

Порівняння EnumSet/EnumMap і звичайних колекцій

Колекція Ключ/елемент Внутрішній устрій Продуктивність Памʼять null
EnumSet
enum
бітова маска O(1) дуже мало не можна
HashSet<Enum>
enum
хеш-таблиця O(1) більше можна
EnumMap
enum
масив за порядковим номером O(1) дуже мало не можна
HashMap<Enum, V>
enum
хеш-таблиця O(1) більше можна

Найкращі практики

  • Використовуйте EnumSet для наборів значень (of, noneOf, allOf, complementOf).
  • Використовуйте EnumMap для асоціативних таблиць, де ключ — enum.
  • Уникайте «гігантських» перерахувань — перерахування із сотнями значень знижують компактність.
  • Не серіалізуйте, якщо enum може змінюватися.
  • Не використовуйте null як ключ або значення.

5. Практика: як використовувати EnumSet і EnumMap у застосунку

Приклад: зберігання ролей користувача

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

Приклад: таблиця переходів станів

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. Типові помилки під час роботи з EnumSet/EnumMap

Помилка № 1: Використання EnumSet/EnumMap з типом, що не є enum.
Ці колекції працюють лише з типами, що є enum.

// EnumSet<String> set = EnumSet.of("A", "B"); // Помилка компіляції!

Помилка № 2: Використання EnumSet/EnumMap для дуже великих перерахувань.
Перерахування на сотні значень знижують компактність. Втім, за споживанням памʼяті це часто все одно краще, ніж HashSet/HashMap для тих самих даних.

Помилка № 3: Зміна enum після серіалізації.
Додавання, видалення або зміна порядку значень після серіалізації призводить до помилок під час читання або до втрати даних.

Помилка № 4: Використання EnumSet/EnumMap із null.
Ані елементи EnumSet, ані ключі/значення EnumMap не можуть бути null.

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

Помилка № 5: Очікування, що EnumSet — це «звичайний» Set.
EnumSet зберігає лише значення свого enum; ви не можете додати туди довільний обʼєкт поза перерахуванням.

Помилка № 6: Використання EnumSet/EnumMap для «змінних» enum.
Якщо перерахування генеруються або замінюються під час виконання (динамічне завантаження/рефлексія), ці структури не працюватимуть коректно.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ