Привет! Сегодня мы поговорим об особом типе данных в Java —
Enum
(сокращенно от английского enumeration — «перечисление»).
В чем же заключается их особенность?
Давай представим, что нам нужно реализовать в программе месяцы.
Казалось бы, в чем проблема? Надо просто определить, какие свойства есть у любого месяца. Пожалуй, нам нужны прежде всего название месяца и число дней в нем.
Решение задачи выглядит довольно простым:
public class Month {
private String name;
private int daysCount;
public Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getDaysCount() {
return daysCount;
}
public void setDaysCount(int daysCount) {
this.daysCount = daysCount;
}
@Override
public String toString() {
return "Month{" +
"name='" + name + '\'' +
", daysCount=" + daysCount +
'}';
}
}
Пожалуйста, полный набор!
У нас есть класс Month
, нужные поля, геттеры-сеттеры, toString()
. Разве что equals()
и hashCode()
надо добавить для полного счастья :) Однако, у нас есть концептуальная проблема.
Как ты, наверное, помнишь, одно из главных преимуществ ООП заключается в том, что оно позволяет легко моделировать сущности из реального мира.
Стул, машина, планета — все эти понятия из обычной жизни легко представить в программе при помощи абстракции.
Проблема в том, что у некоторых сущностей в реальном мире есть строго ограниченный круг значений.
В году всего 4 времени года.
В музыке всего 7 нот.
В календаре всего 12 месяцев.
У Оушена всего 11 друзей (хотя, это спорный вопрос :))
Иными словами, обычный Java-класс не способен смоделировать эти сущности и соблюсти их естественные ограничения.
В нашем классе Month
есть все нужные поля. Но ведь если его будет использовать другой программист, никто не помешает ему создавать совершенно безумные объекты:
public class Main {
Month month1 = new Month("lolkek", 322);
Month month2 = new Month("yahoooooooooooo", 12345);
}
Если такое появится в программе, будет непросто найти виновного! С одной стороны, программист, создавший объекты, мог бы и понять, что класс Month
подразумевает «месяц в году» и не писать подобную дичь.
С другой стороны, он всего лишь пользовался теми возможностями, которые ему предоставил проектировщик класса. Можно назначать любые имена и количество дней? Он и назначил.
Что же в такой ситуации делать?
До выхода версии языка Java 1.5 программистам приходилось, откровенно говоря, выкручиваться :)
В те времена они создавали вот такие конструкции:
public class Month {
private String name;
private int daysCount;
private Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
public static Month JANUARY = new Month("Январь", 31);
public static Month FEBRUARY = new Month("Февраль", 28);
public static Month MARCH = new Month("Март", 31);
@Override
public String toString() {
return "Month{" +
"name='" + name + '\'' +
", daysCount=" + daysCount +
'}';
}
}
Здесь мы упростили количество месяцев до трех вместо двенадцати, чтобы пример был короче.
Такие конструкции позволяли решить поставленную задачу. Варианты создаваемых объектов ограничивались приватным конструктором:
private Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
Программисты, использующие класс, не могли просто создавать объекты Month
. Они вынуждены были пользоваться теми финальными статическими объектами, которые предоставлял разработчик класса.
Это выглядело примерно вот так:
public class Main {
public static void main(String[] args) {
Month january = Month.JANUARY;
System.out.println(january);
}
}
Однако разработчики Java обратили внимание на существующую проблему. Конечно, здорово, что программисты смогли придумать ее решение с помощью имеющихся в языке средств, но оно выглядит не таким уж простым! Необходимо было очевидное решение, доступное даже новичкам.
Так в Java и появился Enum
.
По сути, Enum
— это Java-класс, который предоставляет ограниченный набор значений-объектов.
Вот как он выглядит:
public enum Month {
JANUARY,
FEBRUARY,
MARCH
}
В определении мы указали что Enum
— это Java-класс, но действительно ли это так? Да, и мы даже можем это проверить.
Попробуй, например, унаследовать наш enum Month
от какого-то другого класса:
public abstract class AbstractMonth {
}
//ошибка! No extends clause allowed to enum
public enum Month extends AbstractMonth {
JANUARY,
FEBRUARY,
MARCH
}
Почему так происходит?
Когда мы пишем программе:
public enum Month
компилятор преобразует эту команду в такой код:
public Class Month extends Enum
Как ты уже знаешь, множественное наследование в Java запрещено. Поэтому и наследоваться от AbstractMonth
мы не смогли.
Как эту новую конструкцию, Enum
, можно использовать? И в чем ее отличие от старой конструкции со static final
полями?
Ну, например, старая конструкция не позволяла нам применять свой набор значений в switch
-выражениях.
Представь, что мы хотим создать программу, которая будет напоминать нам о том, какие праздники отмечаются в этом месяце:
public class HolidayReminder {
public void printHolidays(Month month) {
switch (month) {
//ошибка!
case JANUARY:
}
}
}
Здесь, как видишь, компилятор выбрасывает ошибку.
Но после того, как в Java 1.5 появились enum
, все стало гораздо проще:
public enum Month {
JANUARY,
FEBRUARY,
MARCH
}
public class HolidayReminder {
public void printHolidays(Month month) {
switch (month) {
case JANUARY:
System.out.println("7 января будет Рождество!");
break;
case FEBRUARY:
System.out.println("В феврале празднуется День Защитника Отечества - 23 февраля!");
break;
case MARCH:
System.out.println("В марте отмечается Всемирный Женский День - 8 марта!");
break;
}
}
}
public class Main {
public static void main(String[] args) {
HolidayReminder reminder = new HolidayReminder();
reminder.printHolidays(Month.JANUARY);
}
}
Вывод в консоль:
7 января будет Рождество!
Обрати внимание: доступ к объектам Enum
остался статическим, как это было до Java 1.5. Нам не нужно создавать объект Month
для доступа к месяцам.
При работе с перечислениями очень важно не забывать, что Enum
— это полноценный класс.
Это значит, что при необходимости ты можешь определить в нем конструкторы и методы.
К примеру, в предыдущем куске кода мы просто указали значения JANUARY, FEBRUARY, MARCH.
Однако мы можем расширить наш enum Month
вот таким образом:
public enum Month {
JANUARY("Январь", 31),
FEBRUARY("Февраль", 28),
MARCH("Март", 31),
APRIL("Апрель", 30),
MAY("Май", 31),
JUNE("Июнь", 30),
JULY("Июль", 31),
AUGUST("Август", 31),
SEPTEMBER("Сентябрь", 30),
OCTOBER("Октябрь", 31),
NOVEMBER("Ноябрь", 30),
DECEMBER("Декабрь", 31);
private String name;
private int daysCount;
Month(String name, int daysCount) {
this.name = name;
this.daysCount = daysCount;
}
public static Month[] getWinterMonths() {
return new Month[]{DECEMBER, JANUARY, FEBRUARY};
}
public static Month[] getSummerMonths() {
return new Month[]{JUNE, JULY, AUGUST};
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getDaysCount() {
return daysCount;
}
public void setDaysCount(int daysCount) {
this.daysCount = daysCount;
}
@Override
public String toString() {
return "Month{" +
"name='" + name + '\'' +
", daysCount=" + daysCount +
'}';
}
}
Здесь мы добавили к нашему enum
2 поля — название месяца и число дней, конструктор с использованием этих полей, геттеры-сеттеры, метод toString()
, а также 2 статических метода.
Как видишь, никаких проблем с этим не возникло: как мы и сказали ранее, enum
— это полноценный класс:
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
System.out.println(Arrays.toString(Month.getSummerMonths()));
}
}
Вывод в консоль:
[Month{name='Июнь', daysCount=30}, Month{name='Июль', daysCount=31}, Month{name='Август', daysCount=31}]
Напоследок хочу порекомендовать тебе одну крайне полезную книгу по Java, а именно — “Effective Java” Джошуа Блоха.
Автор — один из создателей Java, так что его советам по правильному и грамотному использованию средств языка точно можно доверять :)
Применительно к нашей лекции, советую тебе обратить особое внимание на главу книги, посвященную enum
.
Продуктивного тебе чтения! :)
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ