Тема 12. Enum Перечисления - BelyiZ/JavaCourses GitHub Wiki

Содержание:

  1. Конструкция Enum
  2. Перечисление это класс
  3. Элементы перечисления
  4. Название и порядковый номер элемента Enum
    1. Получение всех элементов перечисления
    2. Добавляем свои методы в Enum класс
  5. Рекомендации в случае использования Enum
  6. Случаи использования Enum
  7. Примеры использования Enum
    1. Специальные коллекции для перечислений
  8. Список литературы/курсов

Часто программисты сталкиваются с необходимостью ограничить множество допустимых значений для некоторого типа данных. Приведем пример. Позиция в футболе — обозначение самого частого местонахождения футболиста на поле и главной задачи которую он выполняет. Всего играют 11 игроков, 10 из них называют полевыми игроками, а одного вратарем. Перечислим задачи полевых игроков:

  1. Нападающие: Форвард, Вингер (2).
  2. Полузащитники: Центральный опорный полузащитник, Центральный полузащитник, Центральный атакующий полузащитник, Фланговый полузащитник.
  3. Защитники: Центральный защитник, Свободный защитник (либеро), Фланговый защитник.

Таким образом, если задача связана с футболистами, то для решения подобных задач во многих языках программирования со статической типизацией предусмотрен специальный тип данных – перечисление (Enum).

То есть перечислимый тип определяется набором связанных констант в качестве его значений.

Перечисление – это в основном список именованных констант. В Java это определяет тип класса. Он может иметь конструкторы, методы и переменные экземпляра. Он создается с помощью ключевого слова Enum. Хотя перечисление определяет тип класса и имеет конструкторы, но не нужно создавать экземпляр перечисления с помощью новой переменной. Переменные перечисления используются и объявляются так же, как переменные примитива.

Особенности:

Enum - это отдельная структура. Enum может находиться в отдельном файле, а может быть частью класса. Но при этом Enum необязательно должен лежать в каком-либо классе. То есть объявление Enum может быть сделано вне Класса или внутри Класса. При таком подходе как бы создаем еще один класс, только вместо слова class пишем Enum Но не возможно объявить Enum внутри метода.

Преимущества:

  1. Enum – это набор именованных констант, который помогает в определении своих собственных типов данных.
  2. Enum в Java в основном улучшает безопасность типов.
  3. Его можно по-разному использовать в примерах переключения.
  4. Enum может быть легко пройден.
  5. Перечисление имеет поля, конструкторы и методы.
  6. 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 равен nullNullPointerException. Об этом часто забывают.

Получение всех элементов перечисления

Иногда необходимо получить список всех элементов 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

  1. Перечисления неявно расширяют класс java.lang.Enum, поэтому типы перечислений не могут расширять другой класс.
  2. Если перечисление содержит поля и методы, определение полей и методов должно всегда идти в списке констант в нем. Кроме того, список констант должен заканчиваться точкой с запятой ";"".
  3. У 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);

Список литературы/курсов

  1. https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html
  2. https://vertex-academy.com/tutorials/ru/perechislenija-enum-java/
  3. https://vertex-academy.com/tutorials/ru/perechisleniya-enum-java-2/
  4. https://vertex-academy.com/tutorials/ru/perechisleniya-enum-v-java-chast-3/

Тема 11. Параметрический полиморфизм (Generics) | Оглавление | Тема 13. Паттерны