Пользователь articles
articles
15 уровень

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

Статья из группы Архив info.javarush.ru
Программируя мы часто сталкиваемся с необходимостью ограничить множество допустимых значений для некоторого типа данных. Так, например, день недели может иметь 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-класса.
Как это понять?