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

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

Стаття з групи 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("lol", 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("1 січня буде Новий рік!");
               break;
           case FEBRUARY:
               System.out.println("2 лютого — День бабака!");
               break;
           case МАРТ:
               System.out.println("У березні відзначається Всесвітній Жіночий День – 8 березня!");
               break;
       }
   }
}



public class Main {

   public static void main(String[] args) {

       HolidayReminder reminder = new HolidayReminder();
       reminder.printHolidays(Month.JANUARY);

   }

}
Виведення в консоль:
1 січня буде Новий рік!
Зверни увагу: доступ до об'єктів 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 Enum. Практичні приклади. Додавання конструкторів і методів - 2Наостанок хочу порекомендувати тобі одну вкрай корисну книгу з Java, а саме – "Effective Java" Джошуа Блоха. Enum. Практичні приклади. Додавання конструкторів і методів - 3Автор – один із творців Java, тож його порадам щодо правильного і грамотного використання засобів мови точно можна довіряти :) Щодо нашої лекції, раджу тобі звернути особливу увагу на розділ книги, присвячений enum. Продуктивного тобі читання! :)
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ