Тема 12. Enum Перечисления - BelyiZ/JavaCourses GitHub Wiki
Содержание:
- Конструкция Enum
- Перечисление это класс
- Элементы перечисления
- Название и порядковый номер элемента Enum
- Рекомендации в случае использования Enum
- Случаи использования Enum
- Примеры использования Enum
- Список литературы/курсов
Часто программисты сталкиваются с необходимостью ограничить множество допустимых значений для некоторого типа данных. Приведем пример. Позиция в футболе — обозначение самого частого местонахождения футболиста на поле и главной задачи которую он выполняет. Всего играют 11 игроков, 10 из них называют полевыми игроками, а одного вратарем. Перечислим задачи полевых игроков:
- Нападающие: Форвард, Вингер (2).
- Полузащитники: Центральный опорный полузащитник, Центральный полузащитник, Центральный атакующий полузащитник, Фланговый полузащитник.
- Защитники: Центральный защитник, Свободный защитник (либеро), Фланговый защитник.
Таким образом, если задача связана с футболистами, то для решения подобных задач во многих языках программирования со статической типизацией предусмотрен специальный тип данных – перечисление (Enum
).
То есть перечислимый тип определяется набором связанных констант в качестве его значений.
Перечисление – это в основном список именованных констант. В Java это определяет тип класса.
Он может иметь конструкторы, методы и переменные экземпляра. Он создается с помощью ключевого слова Enum
.
Хотя перечисление определяет тип класса и имеет конструкторы, но не нужно создавать экземпляр перечисления с помощью новой переменной.
Переменные перечисления используются и объявляются так же, как переменные примитива.
Особенности:
Enum
- это отдельная структура. Enum
может находиться в отдельном файле, а может быть частью класса.
Но при этом Enum
необязательно должен лежать в каком-либо классе. То есть объявление Enum
может быть сделано вне Класса или внутри Класса.
При таком подходе как бы создаем еще один класс, только вместо слова class
пишем Enum
Но не возможно объявить Enum
внутри метода.
Преимущества:
Enum
– это набор именованных констант, который помогает в определении своих собственных типов данных.Enum
в Java в основном улучшает безопасность типов.- Его можно по-разному использовать в примерах переключения.
Enum
может быть легко пройден.- Перечисление имеет поля, конструкторы и методы.
Enum
в основном реализует много интерфейсов, но не может расширять какой-либо класс, потому что он внутренне расширяет классEnum
.
Конструкция Enum
Опишем с помощью Enum
тип данных для хранения местонахождения футболиста на поле и главной задачи которую он выполняет:
Enum Footballplayers{ GOALKEEPER,ATTACKERS, MIDFIELDERS, DEFENDERS}
Константы Enum
GOALKEEPER
, ATTACKERS
, MIDFIELDERS
, DEFENDERS
являются экземплярами анонимного класса — потомка Footballplayers
.
Footballplayers footballplayers = Footballplayers.GOALKEEPER;
if (footballplayers == Footballplayers.GOALKEEPER) footballplayers = Footballplayers.ATTACKERS;{
System.out.println(footballplayers);
}
Footballplayers
и другие типизированные перечисления являются потомками класса Enum<E extends Enum<E>>
и наследуют различные методы, в том числе values()
, toString()
и compareTo()
из этого класса.
Перечисление это класс
Объявляя Enum
мы неявно создаем класс производный от java.lang.Enum
.
Условно конструкция Enum
Footballplayers { ... }
эквивалентна class Footballplayers extends java.lang.Enum { ... }
.
И хотя явным образом наследоваться от java.lang.Enum
нам не позволяет компилятор, все же в том, что Enum
наследуется, легко убедиться с помощью reflection
:
Пример:
System.out.println(Footballplayers.class.getSuperclass());
Будет выведено:
class java.lang.Enum
Наследование автоматически выполняет компилятор Java
.
Условимся называть класс, созданный компилятором для реализации перечисления — Enum
-классом, а возможные значения перечисляемого типа — элементами Enum
.
Элементы перечисления
Элементы Enum Footballplayers {GOALKEEPER, ATTACKERS, MIDFIELDERS, DEFENDERS}
— это статически доступные экземпляры Enum
-класса Footballplayers.
Их статическая доступность позволяет нам выполнять сравнение с помощью оператора сравнения ссылок "==".
Пример:
Footballplayers footballplayers = Footballplayers.GOALKEEPER;
if (footballplayers == Footballplayers.MIDFIELDERS) footballplayers = Footballplayers.WINDEFENDERSTER;
Название и порядковый номер элемента Enum
Как уже было сказано ранее любой Enum
-класс наследует java.lang.Enum
, который содержит ряд методов полезных для всех перечислений.
Пример:
Footballplayers footballplayers = Footballplayers.MIDFIELDERS;
System.out.println("footballplayers.name()=" + footballplayers.name() + " footballplayers.toString()=" + footballplayers.toString() + " footballplayers.ordinal()=" + footballplayers.ordinal());
Получим вывод:
footballplayers.name()=MIDFIELDERS;
footballplayers.toString()=MIDFIELDERS;
footballplayers.ordinal()=0;
Здесь показано использования методов name()
, toString()
и ordinal()
.
Следует обратить внимание, что данные методы Enum
-класс наследует из класса java.lang.Enum
.
Метод name()
возвратил название элемента.
Метод toString()
возвратил содержание элемента.
Метод ordinal()
возвратил порядковый номер определенной константы (нумерация начинается с 0).
Получение элемента Enum
по строковому представлению его имени.
Довольно часто возникает задача получить элемент Enum
по его строковому представлению.
Для этих целей в каждом Enum
-классе компилятор автоматически создает специальный статический метод: public static EnumClass valueOf(String name)
, который возвращает элемент перечисления EnumClass
с названием, равным name
.
Пример:
String name = "MIDFIELDERS";
Footballplayers footballplayers = Footballplayers.valueOf(name);
В результате выполнения кода переменная footballplayers
будет равна Footballplayers.MIDFIELDERS
.
Cледует обратить внимание, что если элемент не будет найден, то будет выброшен IllegalArgumentException
, а в случае, если name равен null
— NullPointerException
.
Об этом часто забывают.
Получение всех элементов перечисления
Иногда необходимо получить список всех элементов Enum
-класса во время выполнения.
Для этих целей в каждом Enum
-классе компилятор создает метод public static EnumClass[] values()
.
Пример:
System.out.println(Arrays.toString(Footballplayers.values()));
Получим вывод:
[GOALKEEPER, ATTACKERS, MIDFIELDERS, DEFENDERS]
Обратим внимание, что ни метод valueOf()
, ни метод values()
не определен в классе java.lang.Enum
.
Вместо этого они автоматически добавляются компилятором на этапе компиляции Enum
-класса.
Добавляем свои методы в Enum класс
Возможно создавать методы, которые производят вычисления на основе значений полей константы перечисления.
Также есть возможность добавлять собственные методы как в Enum
-класс, так и в его элементы:
Пример:
Enum Footballplayers{
GOALKEEPER,ATTACKER;
public Footballplayers opposite () { return this -- GOALKEEPER ? ATTACKER: GOALKEEPER}
}
То же, но с полиморфизмом:
Пример:
Enum Footballplayers{
GOALKEEPER {
public Footballplayers opposite () { return ATTACKER }
}
ATTACKER {
public Footballplayers opposite () { return GOALKEEPER }
}
public abstract Footballplayers opposite ()
}
Последний пример демонстрирует использование наследования в Enum
.
Наследование в Enum
С помощью Enum
в Java можно реализовать иерархию классов, объекты которой создаются в единственном экземпляре и доступны статически.
При этом элементы Enum
могут содержать собственные конструкторы.
Abstract методы
Для класса Enum
также могут быть абстрактные методы. Если у класса есть абстрактный метод, то каждый экземпляр класса должен его реализовывать:
Пример:
public Enum Footballplayers {
GOALKEEPER {
@Override
public String asFootballCase() {
return GOALKEEPER.toString().toFootballCase();
}
},
ATTACKERS{
@Override
public String asFootballCase() {
return ATTACKERS.toString().toFootballCase();
}
},
public abstract String asFootballCase();
}
Обратим внимание на объявление абстрактного метода внизу класса Enum
.
Каждый экземпляр Enum
(каждая константа) определяет собственную реализацию этого метода.
Его использование полезно, когда нужна отдельная реализация метода для каждого экземпляра перечисления.
Перечисления и параметрический полиморфизм
Дело в том, что в Java использование генериков (generics) в Enum
запрещено.
То есть следующий пример не скомпилируется:
Пример:
Enum Type<T> {}
Рекомендации в случае использования Enum
- Перечисления неявно расширяют класс
java.lang.Enum
, поэтому типы перечислений не могут расширять другой класс. - Если перечисление содержит поля и методы, определение полей и методов должно всегда идти в списке констант в нем. Кроме того, список констант должен заканчиваться точкой с запятой ";"".
- У
Enum
есть модификатор доступа. ЕслиEnum
не лежит внутри какого-нибудь класса, он должен быть объявленpublic
. Если сделать егоprivate
, получим ошибку. Если жеEnum
будет "внутри" класса, он может быть объявленprivate
.
Пример:
public class MyClass {
private Enum Footballplayers{ GOALKEEPER,ATTACKERS, MIDFIELDERS, DEFENDERS}
}
Случаи использования Enum
Enum
следует использовать тогда, когда необходимо получить конечное, заранее определенное, адекватно именованное множество уникальных значений.
Примеры использования Enum
В заявлениях if Поскольку перечисления являются константами, часто приходится сравнивать переменную, указывающую на константу перечисления, с возможными константами в типе перечисления:
Пример:
Enum Footballplayers{ GOALKEEPER,ATTACKERS, MIDFIELDERS, DEFENDERS}
if( footballplayers == Footballplayers.GOALKEEPER) {
} else if( footballplayers == Footballplayers.ATTACKERS) {
} else if( footballplayers == Footballplayers.MIDFIELDERS) {
}else if( footballplayers == Footballplayers.DEFENDERS) {
}
Этот код сравнивает переменную Footballplayers
каждой из возможных констант перечисления в перечислении Footballplayers
.
Если одно из значений встречается чаще, чем другие, проверка этого значения в первом операторе if
приведет к лучшей производительности, так как выполняется меньше сравнений в среднем. Это не большая разница, если только сравнения не выполняются.
В выражениях
Если типы перечислений содержат много констант, и нужно проверить переменную по значениям, как показано в предыдущем разделе, использование оператора переключения switch
может быть хорошей идеей:
Пример:
switch(footballplayers) {
case GOALKEEPER : ...; break;
case ATTACKERS : ...; break;
case MIDFIELDERS : ...; break;
case DEFENDERS : ...; break;
}
Где … - код, который нужно выполнить, если переменная уровня соответствует заданному значению константы уровня. Код может быть простой операцией или вызовом метода и не только.
Итерации (for each)
Возможно получить массив всех возможных значений типа Enum
, вызвав его метод static values()
.
Все типы перечислений получают статический метод values()
автоматически компилятором.
Пример:
for(Footballplayers footballplayers : Footballplayers.values()) {
System.out.println(footballplayers);
}
Получим вывод:
GOALKEEPER
ATTACKERS
MIDFIELDERS
DEFENDERS
Обратим внимание, на то как распечатываются имена самих констант. Это одна из областей, где перечисления отличаются от статических конечных констант.
Fields
Возможно добавить поля в перечисление.
Таким образом, каждое значение константы Enum
получает их.
Значения полей должны быть предоставлены конструктору перечисления при определении констант:
Пример:
public Enum Footballplayers {
GOALKEEPER ("Алексей Петров"), //вызываем конструктор со значением "Алексей Петров"
ATTACKERS ("Сергей Забивалов"),
MIDFIELDERS ("Николай Башкоймяч");
private final string footballplayersCode;
private Footballplayers(string footballplayersCode) {
this.footballplayersCode = footballplayersCode;
}
}
Обратим внимание, что перечисление в приведенном выше примере имеет конструктор, который принимает string
.
Он устанавливает поле string
. Когда постоянные значения перечисления определены, значение string
передается в конструктор Java
.
Конструктор Enum
должен быть либо закрытым, либо областью действия пакета (по умолчанию). Возможно использовать публичные или защищенные.
Реализация интерфейса
Enum
может реализовать интерфейс. Enum
в Java
неявно реализует как Serializable
, так и Comparable
интерфейс.
Enum
реализует интерфейс Comparable
для того, чтобы перечисления можно было сравнивать друг с другом при сортировке.
При этом сравнение происходит по ordinal()
перечисления. Посмотрим на сравнение элементов перечисления с помощью метода compareTo()
.
Пример:
Enum Footballplayers{ GOALKEEPER,ATTACKERS, MIDFIELDERS, DEFENDERS}
System.out.println(Footballplayers.MIDFIELDERS.compareTo(Footballplayers.ATTACKERS)); //output: 1
System.out.println(Footballplayers.MIDFIELDERS.compareTo(Footballplayers.MIDFIELDERS)); //output: 0
System.out.println(Footballplayers.MIDFIELDERS.compareTo(Footballplayers.DEFENDERS)); //output: -1
System.out.println(Footballplayers.ATTACKERS.compareTo(Footballplayers.DEFENDERS)); //output: -2
Результат показывает как располагаются значения перечисления относительно друг друга:
Число 1 означает, что значение "MIDFIELDERS" находится правее на одну позицию от значения "ATTACKERS" Число 0 означает, что значение "MIDFIELDERS" равно само себе Число -1 означает, что значение "MIDFIELDERS" находится левее от значения "DEFENDERS" на одну позицию Число -2 означает, что значение "MIDFIELDERS" находится правее от значения "GOALKEEPER" на две позиции.
Использование в коллекции List
Пример:
List<FootballplayersEnum> footballplayerss = new ArrayList<>(List.of(Footballplayers.ATTACKERS, Footballplayers.MIDFIELDERS, Footballplayers.DEFENDERS));
сSystem.out.println(footballplayerss);
Collections.sort(footballplayerss);
System.out.println(footballplayerss);
Метод Collections.sort(footballplayerss)
отсортировал список footballplayerss
благодаря тому, что Enum
реализовывают интерфейс Comparable
.
Реализация метода compareTo() в классе Enum.
Пример:
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() &&
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
Отметим, что:
Сравнивать перечисления возможно только между своими типами. Нельзя сравнивать перечисления типа Footballplayers
с перечислением типа Car
.
Мало того, что компилятор не даст это сделать с помощью своих подсказок так еще и в самом методе есть проверка на тип класса перечислений.
Enum реализует интерфейс Serializable
Но, как и в случае с clone(), воспользоваться им невозможно.
Пример:
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize Enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize Enum");
}
Отметим, что:
Причина того, что эти методы приватные да и к тому же бросают исключения при их вызове такая же, что и в случае с clone()
.
Если бы эта возможность была открыта, тогда легко можно было бы сохранить перечисление в файл, затем считать его обратно и получить на выходе два экземпляра одного значения перечисления.
Этот как два значения числа "1";
Специальные коллекции для перечислений
Возможно использовать такие коллекции как ArrayList
или HashSet
вместе с перечислениями, это будет работать точно так же как и с другими значениями.
Но раз значения перечислений определено заранее, знаем их количество и знаем, что новые значения не будут добавляться в перечисление в работе приложения(в Runtime
) так как это невозможно, то разработчики придумали специальную коллекцию для них.
Эта коллекция работает быстрее и эффективнее обычных, используя, особенности перечислений.
Эта коллекция называется EnumSet
.
При работе с Enum
хорошей практикой является использовать именно коллекцию EnumSet
вместо стандартных коллекций.
EnumSet
Специальная реализация Java Set
, которая может хранить перечисления более эффективно, чем стандартные реализации:
Пример:
EnumSet EnumSet = EnumSet.of(Footballplayers.ATTACKERS, Footballplayers.MIDFIELDERS);
После создания возможно использовать EnumSet
, как и любой другой набор.
EnumMap
Специальная реализация Java Map
, которая может использовать экземпляры перечисления в качестве ключей:
Пример:
EnumMap EnumMap = new EnumMap(Footballplayers.class);
EnumMap.put(Footballplayers.GOALKEEPER , "GOALKEEPER footballplayers");
EnumMap.put(Footballplayers.ATTACKERS, "ATTACKERS footballplayers");
EnumMap.put(Footballplayers.MIDFIELDERS , "MIDFIELDERS footballplayers");
String footballplayersValue = EnumMap.get(Footballplayers.GOALKEEPER);
Список литературы/курсов
- https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html
- https://vertex-academy.com/tutorials/ru/perechislenija-enum-java/
- https://vertex-academy.com/tutorials/ru/perechisleniya-enum-java-2/
- https://vertex-academy.com/tutorials/ru/perechisleniya-enum-v-java-chast-3/
Тема 11. Параметрический полиморфизм (Generics) | Оглавление | Тема 13. Паттерны