Menu

Перечисления (enum) в Java: фиксированный набор именованных констант

Что такое enum в Java, как его объявить, добавить поля и методы, использовать в switch и почему enum лучше кучи констант int или String.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Фиксированный набор именованных значений

Некоторые значения имеют смысл только как элемент небольшого известного списка: дни недели, масти в колоде, состояния, в которых может находиться заказ. Их можно было бы смоделировать кодами int (0 — в ожидании, 1 — отправлено) или строками ("PENDING", "SHIPPED"), но ничто не мешает вызывающему передать 7 или "PNEDING". enum закрывает эту брешь — сам тип допускает только те значения, которые вы объявили.

Day — это совершенно новый тип. Переменная типа Day может хранить ровно одну из этих семи констант (или null) и ничего больше. Компилятор отвергнет Day d = "WEDNESDAY"; или Day d = 2;. Именно эта гарантия и есть весь смысл.

Каждая константа (Day.MONDAY и так далее) — это единственный общий экземпляр, создаваемый один раз. Поскольку MONDAY всегда один, перечисления сравнивают через ==, а не через equals — это один и тот же объект.

switch по перечислению

Перечисления естественно сочетаются со switch. Внутри switch не нужно даже уточнять каждую константу именем типа:

Внутри switch пишите case SATURDAY, а не case Day.SATURDAY — Java сама выводит тип. Главное преимущество перед switch по строке или int в том, что если позже вы добавите новую константу, IDE и проверки полноты смогут указать на каждый switch, который забыл её обработать.

Перебор всех констант

Каждое перечисление автоматически получает статический метод values(), возвращающий все его константы в порядке объявления, а также ordinal(), дающий позицию каждой константы, начиная с нуля:

name() возвращает идентификатор константы в виде String, а values() идеально подходит для построения меню или перебора вариантов. Предостережение: не сохраняйте ordinal() нигде, где данные хранятся постоянно. Если кто-то позже переупорядочит константы, номера сдвинутся, и ваши сохранённые данные сломаются. Считайте ordinal() деталью реализации, а не стабильным идентификатором.

Перечисления — настоящие классы: поля и методы

Именно здесь перечисления Java выходят за рамки enum с именованными целыми числами из других языков. Каждая константа может нести данные, передаваемые через конструктор, и у перечисления могут быть методы. Сначала вы перечисляете константы, даёте каждой её аргументы конструктора, а затем после точки с запятой объявляете поля, конструктор и методы.

Обратите внимание на структуру: сначала идут константы и заканчиваются точкой с запятой, а затем следует остальная часть класса. Конструктор неявно private — вы никогда не сможете написать new Planet(...), потому что единственно допустимые экземпляры — это объявленные сверху. Поля final делают каждую константу неизменяемой, что и нужно для общих синглтонов.

Из строки и обратно

Чтобы превратить строку в соответствующую константу, используйте автоматически сгенерированный valueOf:

valueOf сопоставляет имя константы точно, с учётом регистра, и бросает IllegalArgumentException, если совпадения нет. Это и есть распространённая ловушка: "shipped" не совпадает с SHIPPED. При разборе пользовательского ввода или внешних данных сначала приведите регистр к нужному (input.toUpperCase()) и оберните вызов в try/catch, иначе программа упадёт на первом же неверном значении.

Поведение для каждой константы

Иногда каждой константе нужно вести себя по-разному, а не просто хранить разные данные. Вы можете дать перечислению абстрактный метод и заставить каждую константу предоставить собственную реализацию в небольшом теле:

Это иногда называют шаблоном «метода, специфичного для константы». Он заменяет разросшийся switch по константе поведением, прикреплённым прямо к каждой из них — добавьте новую операцию, и компилятор заставит вас определить её apply, так что ни один случай не забудется.

Когда стоит использовать перечисление

Используйте enum всякий раз, когда значение естественно является элементом небольшого, фиксированного, известного на этапе компиляции набора:

  • Состояния в рабочем процессе (PENDING, SHIPPED, DELIVERED).
  • Категории, режимы или типы (READ, WRITE, EXECUTE).
  • Везде, где у вас был соблазн объявить группу констант public static final int — перечисление даёт те же именованные значения плюс типобезопасность, читаемый toString и поддержку switch в придачу.

Не используйте enum для открытого или определяемого во время выполнения набора (имена пользователей, список стран из базы данных). Перечисления фиксируются на этапе компиляции; если набор меняется во время работы программы, вам нужна обычная коллекция.

Далее: Обобщения (generics)

Вы уже видели, как values() возвращает Planet[], а на предыдущих страницах — использование List<Shape>: этот синтаксис <...> и есть обобщения (generics), способ Java написать один класс или метод, который работает для многих типов, сохраняя полную типобезопасность. Дальше мы разберём, как на самом деле работают List<String> и Map<K, V> и как писать собственные обобщённые типы.

Часто задаваемые вопросы

Что такое enum в Java?

enum — это особый тип, значение которого может быть только одной из фиксированного набора именованных констант, например Day.MONDAY или Status.ACTIVE. Каждая константа — это единственный заранее созданный экземпляр перечисления. Перечисления дают типобезопасность: метод, принимающий Day, всегда получит реальный день, а не строку с опечаткой или int вне диапазона.

Могут ли у enum в Java быть поля и методы?

Да. Перечисление — это полноценный класс. Вы можете передать каждой константе аргументы конструктора, сохранить их в полях private final и добавить методы, которые их используют. Например, MERCURY(3.3e23, 2.4e6) передаёт массу и радиус в конструктор перечисления, а метод surfaceGravity() может вычислить значение на основе этих полей.

В чём разница между values() и valueOf() у enum в Java?

values() возвращает массив всех констант перечисления в порядке объявления — удобно для перебора всех вариантов. valueOf("NAME") делает обратное: ищет константу, имя которой точно совпадает со строкой, и бросает IllegalArgumentException, если совпадения нет. Оба метода генерируются компилятором автоматически.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ