Enum был введен в Java 1.5 в качестве нового типа, поля которого являются константами.
В этом уроке мы узнаем как создать перечисление (Enum), каковы преимущества и особенности использования перечислений. Мы также на примере увидим использование Enum valueOf, значения Enum, EnumSet и EnumMap.
Enum — ключевое слово для создания типа перечисления; очень похоже на класс. Давайте посмотрим пример простого перечисления.
Простой пример использования Enum
ThreadStatesEnum.java
1 2 3 4 5 6 7 8 |
package ua.com.prologistic; public enum ThreadStatesEnum { START, RUNNING, WAITING, DEAD; } |
В примере выше класс ThreadStatesEnum является Enum с фиксированным набором констант START, RUNNING, WAITING и DEAD.
Теперь давайте посмотрим, почему перечисление (Enum) лучше, чем обычные константы полей в классах Java.
Для примера создаем такой же класс с такими же константами
ThreadStatesConstantClass.java
1 2 3 4 5 6 7 8 |
package ua.com.prologistic; public class ThreadStatesConstantClass { public static final int START = 1; public static final int WAITING = 2; public static final int RUNNING = 3; public static final int DEAD = 4; } |
И сразу же посмотрим перечисления и класс с константами в бою:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
... // Показываем преимущества использования Enum private static void benefitsOfEnumOverConstants() { //Enum имеет фиксированные значения simpleEnumExample(ThreadStates.START); simpleEnumExample(ThreadStates.WAITING); simpleEnumExample(ThreadStates.RUNNING); simpleEnumExample(ThreadStates.DEAD); simpleEnumExample(null); simpleConstantsExample(1); simpleConstantsExample(2); simpleConstantsExample(3); simpleConstantsExample(4); //здесь мы можем передать любое число в параметры simpleConstantsExample(5); } private static void simpleEnumExample(ThreadStates th) { if(th == ThreadStates.START) { System.out.println("Поток начал работу"); } else if (th == ThreadStates.WAITING) { System.out.println("Поток ждет"); } else if (th == ThreadStates.RUNNING) { System.out.println("Поток работает"); } else { System.out.println("Поток умер"); } } private static void simpleConstantsExample(int i) { if(i == ThreadStatesConstant.START) { System.out.println("Поток начал работу"); } else if (i == ThreadStatesConstant.WAITING) { System.out.println("Поток ждет"); } else if (i == ThreadStatesConstant.RUNNING) { System.out.println("Поток работает"); } else { System.out.println("Поток умер"); } } |
Если мы посмотрим на пример выше, то сразу увидим две проблемы использования констант, которые решаются с помощью Enam:
- Мы можем передать любой
int
в параметр методаsimpleConstantsExample(int i)
. Эта проблема решается только фиксированным значениемsimpleEnumExample(ThreadStates th)
, что обеспечивает безопасность. - Мы можем изменить значение константы
int
вThreadStatesConstantClass
классе, но программа не будет бросать никаких исключений, хотя может работать не так, как ожидалось. Но если мы изменим саму константу Enum, то получим исключение.
Результатом выполнения программы будет следующее:
1 2 3 4 5 6 7 8 9 10 |
Поток начал работу Поток ждет Поток работает Поток умер Поток умер Поток начал работу Поток ждет Поток работает Поток умер Поток умер |
Продвинутые возможности Enum (перечислений)
А теперь давайте рассмотрим продвинутые возможности перечислений (Enum) на тестовом классе:
ThreadStatesEnum.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
package ua.com.prologistic; import java.io.Closeable; import java.io.IOException; // Здесь показано все, что мы можем сделать с Enum public enum ThreadStatesEnum implements Closeable{ START(1){ @Override public String toString(){ return "Реализация START. Приоритет : " + getPriority(); } @Override public String getDetail() { return "START"; } }, RUNNING(2){ @Override public String getDetail() { return "RUNNING"; } }, WAITING(3){ @Override public String getDetail() { return "WAITING"; } }, DEAD(4){ @Override public String getDetail() { return "DEAD"; } }; private int priority; public abstract String getDetail(); //Enum конструкторы должны всегда быть private. private ThreadStatesEnum(int i){ priority = i; } //У Enum могут быть методы public int getPriority(){ return this.priority; } public void setPriority(int p){ this.priority = p; } //Enum может переопределять методы @Override public String toString(){ return "Стандартная реализация ThreadStatesConstructors. Приоритет : " + getPriority(); } @Override public void close() throws IOException { System.out.println("Закрытие Enum"); } } |
Правила использования Enum
- Все перечисления в Java неявно расширяют
java.lang.Enum
класс, который расширяет классObject
и реализуетSerializable
иComparable
интерфейсы, так что Enum не может наследовать классы. - Нельзя заканчивать имя пакета словом enum, например,
com.javadevblog.enum
— недопустимое имя пакета. - Перечисления в Java могут реализовывать интерфейсы. (На примере выше есть Enum, который реализовывает интерфейс
Closeable
. - Enum конструкторы в Java всегда private.
- Нельзя создать экземпляр перечисления, используя оператор
new
. - Мы можем создавать абстрактные методы в Enum, поэтому все поля Enum должны реализовывать абстрактный метод. (В приведенном выше примере метод
g
etDetail()
является абстрактным и все поля в Enum реализовали его. - Мы можем определить метод в Enum, а поля могут переопределять их.
- Все поля в Enum имеют пространство имен, поэтому мы можем использовать поле только с именем класса:
ThreadStates.START
- Перечисления могут быть использованы в
switch
. Пример использования увидим чуть позже в этом уроке. - Мы можем изменять существующее перечисление не нарушая существующей функциональности. Например, мы можем добавить новое поле
NEW
вThreadStatesClass
и это никак не повлияет на существующую функциональность. - Хорошим тоном считается написание большими буквами и нижнее подчеркивание для пробелом. Например,
EAST, WEST, EAST_DIRECTION
и т.д. - Enum константы неявно static и final
- Enum константы являются final, но ее можно изменять. Например, мы можем использовать метод
setPriority()
, чтобы изменить приоритет констант в Enum. (Мы увидим его на практике в примере ниже). - Мы может безопасно сравнивать константы с помощью «
==
» и методаequals()
. Они оба будут давать тот же результат.
Продвинутые возможности Enum на примере
Мы уже знаем правила использование Enum, его расширенные возможности, а также подводные камни. Самое время испытать это все на примере:
JavaEnumExamples.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
package ua.com.prologistic; import java.io.IOException; import java.util.EnumMap; import java.util.EnumSet; import java.util.Set; public class JavaEnumExamples { public static void main(String[] args) throws IOException { usingEnumMethods(); usingEnumValueOf(); usingEnumValues(); usingEnumInSwitch(ThreadStatesEnum.START); usingEnumInSwitch(ThreadStatesEnum.DEAD); usingEnumMap(); usingEnumSet(); } private static void usingEnumSet() { EnumSet<ThreadStatesEnum> enumSet = EnumSet.allOf(ThreadStatesEnum.class); for(ThreadStatesEnum tsenum : enumSet){ System.out.println("Используем EnumSet, приоритет = " + tsenum.getPriority()); } } private static void usingEnumMap() { EnumMap<ThreadStates, String> enumMap = new EnumMap<ThreadStates,String>(ThreadStates.class); enumMap.put(ThreadStates.START, "Поток начал работу"); enumMap.put(ThreadStates.RUNNING, "Поток ждет"); enumMap.put(ThreadStates.WAITING, "Поток работает"); enumMap.put(ThreadStates.DEAD, "Поток умер"); Set<ThreadStates> keySet = enumMap.keySet(); for(ThreadStates key : keySet){ System.out.println("ключ = " + key.toString() + " :: значение = " + enumMap.get(key)); } } private static void usingEnumInSwitch(ThreadStatesEnum th) { switch (th){ case START: System.out.println("Состояние потока: START"); break; case WAITING: System.out.println("Состояние потока: WAITING"); break; case RUNNING: System.out.println("Состояние потока: RUNNING"); break; case DEAD: System.out.println("Состояние потока: DEAD"); } } private static void usingEnumValues() { ThreadStatesEnum[] thArray = ThreadStatesEnum.values(); for(ThreadStatesEnum th : thArray){ System.out.println(th.toString() + "::приоритет = " + th.getPriority()); } } private static void usingEnumValueOf() { ThreadStatesEnum th = Enum.valueOf(ThreadStatesEnum.class, "START"); System.out.println("th приоритет = " + th.getPriority()); } private static void usingEnumMethods() throws IOException { ThreadStatesEnum thc = ThreadStatesEnum.DEAD; System.out.println("приоритет : " + thc.getPriority()); thc = ThreadStatesEnum.DEAD; System.out.println("Используем переопределенный метод. " + thc.toString()); thc = ThreadStatesEnum.START; System.out.println("Используем переопределенный метод. " + thc.toString()); thc.setPriority(10); System.out.println("Константа Enum изменила значение = " + thc.getPriority()); thc.close(); } } |
Результат выполнения программы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
приоритет:4 Используем переопределенный метод . Стандартная реализация ThreadStatesConstructors. Приоритет : 4 Используем переопределенный метод . Реализация START. Приоритет :1 Константа Enum изменила значение = 10 Закрытие Enum th приоритет = 10 Реализация START. Приоритет : 10::приоритет = 10 Стандартная реализация ThreadStatesConstructors. Приоритет : 2::приоритет = 2 Стандартная реализация ThreadStatesConstructors. Приоритет : 3::приоритет = 3 Стандартная реализация ThreadStatesConstructors. Приоритет : 4::приоритет = 4 Состояние потока: START Состояние потока: DEAD ключ = START :: значение = Поток начал работу ключ = RUNNING :: значение = Поток работает ключ = WAITING :: значение = Поток ждет ключ = DEAD :: значение = Поток умер Используем EnumSet, приоритет = 10 Используем EnumSet, приоритет = 2 Используем EnumSet, приоритет = 3 Используем EnumSet, приоритет = 4 |
А теперь подробно по каждой функции
- Метод
usingEnumMethods()
показывает как создать объект перечисления и как мы можем использовать его методы. - Метод
usingEnumValueOf()
показывает использование методаvalueOf(enumType, name)
класса java.util.Enum, с помощью которого мы можем создать объект перечисления из строки. - Метод
usingEnumValues()
показывает использование методаvalues()
, который возвращает массив, содержащий все значения перечисления в том порядке, в котором они были объявлены. Обратите внимание, что этот метод автоматически генерируется компилятором Java для каждого перечисления. Вы не найдете реализацию методаvalues()
в классе java.util.Enum. - Метод
usingEnumInSwitch()
показывает, как использовать константы Enum в switch. - Метод
usingEnumMap()
показывает использование java.util.EnumMap, который ввелся в Java 1.5 Collections Framework. EnumMap является реализацией Map. Все ключи в EnumMap должны быть одного типа, указанного явно или неявно, когда карта будет создана. Мы не можем использоватьnull
в качестве ключа для EnumMap.
Вот и все, что нужно знать о Enum в Java для комфортной и эффективной работы.
Делитесь материалом и следите за обновлениями на javadevblog.com
Подскажите, здесь нет ошибки?
ThreadStatesEnum.java
//Enum конструкторы должны всегда быть private.
private ThreadStatesEnumClass(int i){
priority = i;
}
Я думаю должно быть без «class»
private ThreadStatesEnum(int i){
priority = i;
}
Да, Вы правы. Подправил код. Спасибо за замечание!
Читал, что Enum значительно тяжелее, чем «магические» константы. Примерно в пять раз.
Насколько целесообразно использовать их?
Конструктора нет в ThreadStatesEnum.java