JavaRush /Java блог /Java Developer /Enum в Java. Практические примеры. Добавление конструктор...
Автор
Александр Выпирайленко
Java-разработчик в Toshiba Global Commerce Solutions

Enum в Java. Практические примеры. Добавление конструкторов и методов

Статья из группы Java Developer
Привет! Сегодня мы поговорим об особом типе данных в Java — Enum (сокращенно от английского enumeration — «перечисление»). В чем же заключается их особенность? Давай представим, что нам нужно реализовать в программе месяцы. Enum. Практические примеры. Добавление конструкторов и методов - 1Казалось бы, в чем проблема? Надо просто определить, какие свойства есть у любого месяца. Пожалуй, нам нужны прежде всего название месяца и число дней в нем. Решение задачи выглядит довольно простым:

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” Джошуа Блоха. Enum. Практические примеры. Добавление конструкторов и методов - 3Автор — один из создателей Java, так что его советам по правильному и грамотному использованию средств языка точно можно доверять :) Применительно к нашей лекции, советую тебе обратить особое внимание на главу книги, посвященную enum. Продуктивного тебе чтения! :)
Комментарии (77)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
2 февраля 2024
так я поримаю, просто для наглядности в конце сеттеры добавил, ибо зачем в таком конкретном случае)
Максим Li Уровень 36
6 декабря 2023
Ок!
Даня Семяк Уровень 26
17 сентября 2023
fine, nice!
Grock Уровень 44
15 августа 2023
fine Nice + статья в копилку Вопрос риторический.
Серж Уровень 42
28 июля 2023
Nice
27 июля 2023
Кто и зачем на каждой лекции пишет "fine", "+ статья в копилку", "Nice"?!🤯 Вопрос риторический.
Alexander Rozenberg Уровень 32
18 июля 2023
fine
No Name Уровень 32
11 июня 2023
+ статья в копилку
Ислам Уровень 33
31 мая 2023
Nice
Lexoid Уровень 40
24 мая 2023
В примере, где было показано, как программисты выкручивались до появления такой замечательной конструкции, как enum, необходимо добавить модификатор `final` к переменным, иначе теряется весь смысл. Похоже, что этот момент просто упустили. Если мы говорим об именованных константах, то они всегда должны быть public static final (именно такими они и являются в классах-перечислениях).