Что, если вы хотите в своей программе обеспечить использование только определённых значений для своего типа данных (т.е. вашего класса)? Например, только 7 специальных объектов класса DayOfWeek, соответствующих дням недели. Или только 12 специальных объектов класса Month, соответствующих месяцам. Самый удобный способ это сделать – использовать перечисления (enum).
Давайте познакомимся с такой полезной штукой, как перечисления в Java.
Смысл перечислений
Перечисления (enum) в Java – это специальный тип данных, который представляет из себя набор именованных констант (значений). Основная идея в том, чтобы создать тип данных, который может принимать только определённые значения. Это делает код гораздо более читаемым и безопасным. Сравните:
1 |
if (dayOfWeek == 1) |
1 |
if (dayOfWeek == DayOfWeek.TUESDAY) |
Единица – это ничто. Единица – это магическая константа. Чтобы понять, что означает единица, нужно приложить дополнительные усилия. Чтобы понять, что означает TUESDAY, усилий нужно гораздо меньше. То есть вы сберегаете свои программистские ресурсы на чтении кода, и направляете их на его разработку. В этом суть!
Кто-то может возразить, что можно использовать обычные переменные, объявленные как final, вместо перечислений. Например, где-нибудь в коде объявить final int TUESDAY = 1, и спокойно это использовать. Да, можно. И это уже будет лучше, чем писать просто единицу. Однако: применение enum-ов позволяет логично разбросать различные константы по сферам их применения, не смешивая всё в одном месте. В перечислении DayOfWeek хранятся дни недели, в перечислении UserRole хранятся роли пользователей. Всё на своих местах. Вдобавок, и DayOfWeek, и UserRole можно использовать в различных классах.
Более того, enum-ы обладают большими возможностями, чем обычные константы. Их сущности могут иметь свои поля и методы. Но давайте обо всём по порядку.
Как создать перечисление
Для создания перечисления используется ключевое слово enum. Давайте рассмотрим пример перечисления времён года. Их всего четыре. Не больше, не меньше: WINTER, SPRING, SUMMER и AUTUMN.
1 2 3 4 5 6 |
public enum Season { WINTER, SPRING, SUMMER, AUTUMN } |
Согласно соглашению о кодировании, константы прописываются в верхнем регистре с нижними подчёркиваниями в качестве разделителей слов (uppercase). Так как сущности enum-ов – константы, то они именно в таком стиле и пишутся.
Теперь можно использовать эти значения из enum-а Season в других местах в коде. Например, в конструкции switch-case:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Season currentSeason = Season.SUMMER; switch (currentSeason) { case WINTER: System.out.println("Бррр! Холодно!"); break; case SPRING: System.out.println("Цветут цветы"); break; case SUMMER: System.out.println("Наслаждайтесь солнечными деньками"); break; case AUTUMN: System.out.println("Листопад..."); break; } |
Поля и методы в enum
Что делает enum-ы гораздо более крутыми, чем обычные константы, так это возможность иметь собственные поля и методы, совсем как самые настоящие классы. Одна тонкость: конструктор enum-а всегда будет private и изменить это нельзя. Это логично – чтобы извне нельзя было создать неподходящую сущность (например, 8-ой день недели с названием “qwerty123”).
Добавим в созданный enum Season описание прямо внутри него. Оно будет храниться в приватном поле description. Публичным методом getDescription() его можно будет получить извне.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public enum Season { WINTER("Бррр! Холодно!"), SPRING("Цветут цветы"), SUMMER("Наслаждайтесь солнечными деньками"), AUTUMN("Листопад..."); private final String description; private Season(String description) { this.description = description; } public String getDescription() { return description; } } |
Протестируем:
1 2 3 4 5 |
public class MyProject { public static void main(String[] args) { System.out.println(Season.WINTER.getDescription()); } } |
Результат именно такой, как и стоило ожидать. С помощью метода getDescription() в консоль было выведено описание поры года WINTER. И так будет со всеми сущностями enum-а Season. Конечно, хранить можно не только строки. А методы могут быть посложнее геттеров.
Занимательные факты про перечисления
Для того, чтобы получить название элемента перечисления так, как оно прописано в коде, можно воспользоваться методом name():
1 2 3 4 5 |
public class MyProject { public static void main(String[] args) { System.out.println(Season.WINTER.name()); } } |
В этом плане метод name() подходит больше, чем toString(), так как toString() впоследствии может быть переопределён разработчиком и возвращать какое-либо другое строковое представление, а не то название элемента, какое прописано непосредственно в коде.
Перечисления в Java могут наследоваться от интерфейсов:
1 2 3 4 5 6 7 |
public interface Weather { /** * получить строку-описание прогноза погоды */ String getForecast(); } |
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 |
public enum Season implements Weather { WINTER("Бррр! Холодно!"), SPRING("Цветут цветы"), SUMMER("Наслаждайтесь солнечными деньками"), AUTUMN("Листопад..."); private final String description; private Season(String description) { this.description = description; } public String getDescription() { return description; } /** * получить строку-описание прогноза погоды */ @Override public String getForecast() { return "Ожидаются умеренные для данной поры года температуры " + "и стандартные осадки. Подробнее: " + description; } } |
При этом перечисления не могут наследоваться от классов!
На уровне внутренних механизмов, enum в Java являются особым типом класса. Каждый элемент перечисления является экземпляром этого класса. При этом перечисление неявно наследуется от класса Enum, который в свою очередь наследуется от класса Object. Таким образом, элементы перечисления унаследованы от Object. Посему можно использовать методы, определенные в Object, такие как toString(), equals() и hashCode().
Перечисления – это не какая-то экзотика, к которой прибегаешь чисто в целях изучения. Нет. Это удобная штука, которую лично я часто применяю на практике. Именуйте константы и будет вам счастье!