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 после сериализации почти гарантированно приведёт к исключению при чтении.

Best practice:

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

4. Полезные нюансы

Сравнение EnumSet/EnumMap и обычных коллекций

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

Best practices

  • Используйте 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 для очень больших enum.
Перечисления на сотни значений ухудшают компактность. Тем не менее это всё ещё чаще лучше по памяти, чем 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.
Если перечисления генерируются или подменяются в рантайме (динамическая загрузка/рефлексия), эти структуры работать корректно не будут.

1
Задача
JAVA 25 SELF, 28 уровень, 3 лекция
Недоступна
Планирование идеального уикенда 🏖️
Планирование идеального уикенда 🏖️
1
Задача
JAVA 25 SELF, 28 уровень, 3 лекция
Недоступна
Динамическое ценообразование в арт-галерее "Вдохновение" 🎨
Динамическое ценообразование в арт-галерее "Вдохновение" 🎨
Комментарии (2)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Yaroslav Уровень 43
22 декабря 2025

Ни элементы EnumSet, ни ключи/значения EnumMap не могут быть null.
Значение EnumMap может быть null.
Александр Уровень 50
13 ноября 2025
Малопонятно. Пропущу. Подозреваю, что кроме этой лекции и двух задач EnumSet/EnumMap больше не встретится