JavaRush /Java блог /Архив info.javarush /Перечисления в Java (java enum)
articles
15 уровень

Перечисления в Java (java enum)

Статья из группы Архив info.javarush
Программируя мы часто сталкиваемся с необходимостью ограничить множество допустимых значений для некоторого типа данных. Так, например, день недели может иметь 7 разных значений, месяц в году – 12, а время года – 4. Для решения подобных задач во многих языках программирования со статической типизацией предусмотрен специальный тип данных – перечисление (enum). В Java перечисление появилось не сразу. Специализированная языковая конструкция enum была введена начиная с версии 1.5. До этого момента программисты использовали другие методы для реализации перечислений. Перечисления в Java (java enum) - 1

Конструкция enum

Начнем с примера. Давайте опишем с помощью enum тип данных для хранения времени года:

enum Season { WINTER, SPRING, SUMMER, AUTUMN }
Ну и простой пример его использования:

Season season = Season.SPRING;
if (season == Season.SPRING) season = Season.SUMMER;
System.out.println(season);
В результате выполнения которого на консоль будет выведено SUMMER. Думаю, что пример очевиден и в пояснениях не нуждается.

Перечисление — это класс

Объявляя enum мы неявно создаем класс производный от java.lang.Enum. Условно конструкция enum Season { ... } эквивалентна class Season extends java.lang.Enum { ... }. И хотя явным образом наследоваться от java.lang.Enum нам не позволяет компилятор, все же в том, что enum наследуется, легко убедиться с помощью reflection:

System.out.println(Season.class.getSuperclass());
На консоль будет выведено:

class java.lang.Enum
Собственно наследование за нас автоматически выполняет компилятор Java. Далее давайте условимся называть класс, созданный компилятором для реализации перечисления — enum-классом, а возможные значения перечисляемого типа — элементами enum-a.

Элементы перечисления — экземпляры enum-класса, доступные статически

Элементы enum Season (WINTER, SPRING и т.д.) — это статически доступные экземпляры enum-класса Season. Их статическая доступность позволяет нам выполнять сравнение с помощью оператора сравнения ссылок ==. Пример:

Season season = Season.SUMMER;
if (season == Season.AUTUMN) season = Season.WINTER;

Название и порядковый номер элемента enum

Как уже было сказано ранее любой enum-класс наследует java.lang.Enum, который содержит ряд методов полезных для всех перечислений. Пример:

Season season = Season.WINTER;
System.out.println("season.name()=" + season.name() + " season.toString()=" + season.toString() + " season.ordinal()=" + season.ordinal());
Будет выведено:

season.name()=WINTER season.toString()=WINTER season.ordinal()=0
Здесь показано использования методов name(), toString() и ordinal(). Семантика методов — очевидна. Следует обратить внимание, что данные методы enum-класс наследует из класса java.lang.Enum. Получение элемента enum по строковому представлению его имениДовольно часто возникает задача получить элемент enum по его строковому представлению. Для этих целей в каждом enum-классе компилятор автоматически создает специальный статический метод: public static EnumClass valueOf(String name), который возвращает элемент перечисления EnumClass с названием, равным name. Пример использования:

String name = "WINTER";
Season season = Season.valueOf(name);
В результате выполнения кода переменная season будет равна Season.WINTER. Cледует обратить внимание, что если элемент не будет найден, то будет выброшен IllegalArgumentException, а в случае, если name равен nullNullPointerException. Об этом, кстати, часто забывают. Почему-то многие твердо уверенны, что если функция принимает один аргумент и при некоторых условиях выбрасывает IllegalArgumentException, то при передачи туда null, также будет непременно выброшен IllegalArgumentException. Но это не относится к делу. Продолжим. Получение всех элементов перечисления Иногда необходимо получить список всех элементов enum-класса во время выполнения. Для этих целей в каждом enum-классе компилятор создает метод public static EnumClass[] values(). Пример использования:

System.out.println(Arrays.toString(Season.values()));
Получим вывод:

[WINTER, SPRING, SUMMER, AUTUMN]
Обратите внимание, что ни метод valueOf(), ни метод values() не определен в классе java.lang.Enum. Вместо этого они автоматически добавляются компилятором на этапе компиляции enum-класса. Добавляем свои методы в enum-класс У Вас есть возможность добавлять собственные методы как в enum-класс, так и в его элементы: Перечисления в Java (java enum) - 2То же, но с полиморфизмом: Перечисления в Java (java enum) - 3Последний пример демонстрирует использование наследования в enum. Об этом — далее. Наследование в enum С помощью enum в Java можно реализовать иерархию классов, объекты которой создаются в единственном экземпляре и доступны статически. При этом элементы enum могут содержать собственные конструкторы. Приведем пример: Перечисления в Java (java enum) - 4Здесь объявляется перечисление Type с тремя элементами INT, INTEGER и STRING. Компилятор создаст следующие классы и объекты:
  • Type — класс производный от java.lang.Enum
  • INT — объект 1-го класса производного от Type
  • INTEGER — объект 2-го класса производного от Type
  • STRING — объект 3-го класса производного от Type
Три производных класса будут созданы с полиморфным методом Object parse(String) и конструктором Type(..., boolean). При этом объекты классов INT, INTEGER и STRING существуют в единственном экземпляре и доступны статически. В этом можно убедится:

System.out.println(Type.class);
System.out.println(Type.INT.getClass() + " " + Type.INT.getClass().getSuperclass());
System.out.println(Type.INTEGER.getClass() + " " + Type.INTEGER.getClass().getSuperclass());
System.out.println(Type.STRING.getClass()  + " " + Type.STRING.getClass().getSuperclass());
Получим вывод:

class Type
class Type$1 class Type
class Type$2 class Type
class Type$3 class Type
Видно, что компилятор создал класс Type и 3 nested класса, производных от Type.

Декомпилированный enum-class с наследованием

В подтверждение вышесказанному приведем еще результат декомпиляции перечисления Type из примера выше: Перечисления в Java (java enum) - 5

Перечисления и параметрический полиморфизм

У читателя может возникнуть вопрос: "почему вышеуказанное перечисление Type не использует генерики (generics)?". Дело в том, что в Java использование генериков в enum запрещено. Так следующий пример не скомпилируется:

enum Type<T> {}

Дальнейшее изучение

Для более глубокого понимания того, как работают перечисления в Java рекомендую ознакомиться с исходными кодами класса java.lang.Enum, а также воспользоваться декопмилятором Jad для изучения сгенерированного кода. Более того, изучение исходных кодов библиотеки Java абсолютно необходимо для понимания принципов работы многих механизмов в Java и полезно как эталон объектно-ориентированного дизайна. Cсылка на первоисточник: http://alexander.lds.lg.ua/
Комментарии (5)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
namor Уровень 1
14 января 2020
да в самом деле. зачем следить за актуальностью ссылок? как так "это можно автоматизировать" 😂
Alexey Andreev Уровень 18
25 декабря 2019
Ссылка не открывается...
Po4ercoved Уровень 22
14 ноября 2019
Type extends Enum Как это вообще компилируется?
Neyron Уровень 40
16 июля 2016
Обратите внимание, что ни метод valueOf(), ни метод values() не определен в классе java.lang.Enum. Вместо этого они автоматически добавляются компилятором на этапе компиляции enum-класса.
Как это понять?