Привіт! На сьогоднішньому занятті ми продовжимо розглядати тему вкладених класів. Настала черга останньої групи — анонімних внутрішніх класів у Java. Давай повернемося до нашої схеми: Анонимные классы - 2Як і локальні класи, про які ми говорили в минулій лекції, анонімні — підвид внутрішніх класів. У них також є декілька схожостей і відмінностей між собою. Але для початку давай розберемося: чому ж вони, власне, називаються «анонімними»? Для цього розглянемо простий приклад. Уяви, що в нас є основна програма, яка постійно працює і щось робить. Ми хочемо створити для цієї програми систему моніторингу з кількох модулів. Один модуль буде відстежувати загальні показники роботи та вести лог, другий — фіксувати і реєструвати помилки в журналі помилок, третій — відстежувати підозрілу активність: наприклад, спроби несанкціонованого доступу та інші пов'язані з безпекою речі. Оскільки всі три модулі повинні, по суті, просто стартувати на початку програми і працювати у фоновому режимі, буде гарною ідеєю створити для них спільний інтерфейс:

public interface MonitoringSystem {
  
   public void startMonitoring();
}
Його будуть імплементувати 3 конкретних класи:

public class GeneralIndicatorsMonitoringModule implements MonitoringSystem {
   
@Override
   public void startMonitoring() {
       System.out.println("Моніторинг загальних показників стартував!");
   }
}


public class ErrorMonitoringModule implements MonitoringSystem {

   @Override
   public void startMonitoring() {
       System.out.println("Моніторинг відстеження помилок стартував!");
   }
}


public class SecurityModule implements MonitoringSystem {

   @Override
   public void startMonitoring() {
       System.out.println("Моніторинг безпеки стартував!");
   }
}
Здавалося б, усе гаразд. У нас є доволі зрозуміла система з кількох модулів. У кожного з них є власна поведінка. Якщо нам знадобляться нові модулі, ми зможемо їх додати, адже у нас є інтерфейс, який досить легко імплементувати. Але давай подумаємо про те, як буде працювати наша система моніторингу. Анонимные классы - 3По суті, ми повинні просто створити 3 об'єкти — GeneralIndicatorsMonitoringModule, ErrorMonitoringModule, SecurityModule — і викликати метод startMonitoring() у кожного з них. Тобто, все, що потрібно зробити — створити 3 об'єкти і викликати у них 1 метод.

public class Main {

   public static void main(String[] args) {

       GeneralIndicatorsMonitoringModule generalModule = new GeneralIndicatorsMonitoringModule();
       ErrorMonitoringModule errorModule = new ErrorMonitoringModule();
       SecurityModule securityModule = new SecurityModule();

       generalModule.startMonitoring();
       errorModule.startMonitoring();
       securityModule.startMonitoring();
   }
}
Вивід у консоль:

Моніторинг загальних показників стартував!
Моніторинг відстеження помилок стартував!
Моніторинг безпеки стартував!
І для такої невеликої роботи ми написали цілу систему: 3 класи і один інтерфейс! І все це — заради 6 рядків коду. З іншого боку, які у нас варіанти? Так, не дуже приємно, що ми написали такі «одноразові» класи. Але як ми можемо це виправити? Тут нам і приходять на допомогу анонімні внутрішні класи! Ось як вони виглядають у нашому випадку:

public class Main {

   public static void main(String[] args) {

       MonitoringSystem generalModule = new MonitoringSystem() {
           @Override
           public void startMonitoring() {
               System.out.println("Моніторинг загальних показників стартував!");
           }
       };

       

           MonitoringSystem errorModule = new MonitoringSystem() {
           @Override
           public void startMonitoring() {
               System.out.println("Моніторинг відстеження помилок стартував!");
           }
       };

       MonitoringSystem securityModule = new MonitoringSystem() {
           @Override
           public void startMonitoring() {
               System.out.println("Моніторинг безпеки стартував!");
           }
       };

       generalModule.startMonitoring();
       errorModule.startMonitoring();
       securityModule.startMonitoring();
   }
}
Давай розбиратися, що тут відбувається! Виглядає так, ніби ми створюємо об'єкт інтерфейсу:

MonitoringSystem generalModule = new MonitoringSystem() {
   
@Override
   public void startMonitoring() {
       System.out.println("Моніторинг загальних показників стартував!");
   }
};
Але ж ми давно знаємо, що створювати об'єкти інтерфейсів не можна! Так і є, не можна. Насправді ми цього і не робимо. У той момент, коли ми пишемо:

MonitoringSystem generalModule = new MonitoringSystem() {
   
};
всередині Java-машини відбувається наступне:
  1. Створюється безіменний Java-клас, що реалізує інтерфейс MonitoringSystem.
  2. Компилятор, побачивши такий клас, вимагає від тебе реалізувати всі методи інтерфейсу MonitoringSystem (ми це і зробили 3 рази).
  3. Створюється один об'єкт цього класу. Зверни увагу на код:

MonitoringSystem generalModule = new MonitoringSystem() {
   
};
В кінці стоїть крапка з комою! Вона стоїть там не просто так. Ми одночасно оголошуємо клас (через фігурні дужки) і створюємо його об'єкт за допомогою (); Кожен із наших трьох об'єктів перевизначив метод startMonitoring() по-своєму. У кінці ми просто викликаємо цей метод у кожного з них:

generalModule.startMonitoring();
errorModule.startMonitoring();
securityModule.startMonitoring();
Вивід у консоль:

Моніторинг загальних показників стартував!
Моніторинг відстеження помилок стартував!
Моніторинг безпеки стартував!
Ось і все! Ми виконали своє завдання: створили три об'єкти MonitoringSystem, перевизначили його трьома різними способами і викликали тричі. Усі три модулі успішно запущені і працюють. При цьому структура нашої програми стала значно простішою! Адже класи GeneralIndicatorsMonitoringModule, ErrorMonitoringModule, SecurityModule тепер взагалі можна видалити з програми! Вони нам просто не потрібні — ми прекрасно впоралися і без них. Якщо кожному з наших анонімних класів-модулів знадобиться якесь відмінне поведінка, свої специфічні методи, яких немає в інших, ми легко можемо дописати їх:

MonitoringSystem generalModule = new MonitoringSystem() {
  
   @Override
   public void startMonitoring() {
       System.out.println("Моніторинг загальних показників стартував!");
   }
  
   public void someSpecificMethod() {

       System.out.println("Специфічний метод тільки для першого модуля");
   }
};
У документації Oracle наведена хороша рекомендація: «Застосовуйте анонімні класи, якщо вам потрібен локальний клас для одноразового використання». Анонімний клас — це повноцінний внутрішній клас. Тому він має доступ до змінних зовнішнього класу, включаючи статичні і приватні:

public class Main {

   private static int currentErrorsCount = 23;

   public static void main(String[] args) {

       MonitoringSystem errorModule = new MonitoringSystem() {
          
           @Override
           public void startMonitoring() {
               System.out.println("Моніторинг відстеження помилок стартував!");
           }

           public int getCurrentErrorsCount() {

               return currentErrorsCount;
           }
       };
   }
}
Є у них дещо спільне і з локальними класами: вони видно лише всередині того методу, в якому визначені. У прикладі вище, будь-які спроби звернутися до об'єкта errorModule за межами методу main() будуть невдалими. І ще одне важливе обмеження, яке дісталося анонімним класам від їхніх «предків» — внутрішніх класів: анонімний клас не може містити статичні змінні і методи. Якщо ми спробуємо зробити метод getCurrentErrorsCount() з прикладу вище статичним, компілятор викине помилку:

//помилка! Inner classes cannot have static declarations
public static int getCurrentErrorsCount() {

   return currentErrorsCount;
}
Той самий результат ми отримаємо, якщо спробуємо оголосити статичну змінну:

MonitoringSystem errorModule = new MonitoringSystem() {

   //помилка! Inner classes cannot have static declarations!
   static int staticInt = 10;

   @Override
   public void startMonitoring() {
       System.out.println("Моніторинг відстеження помилок стартував!");
   }

};
Що ж ми вивчатимемо про вкладені класи далі? Скоро ти обов'язково дізнаєшся! :)