JavaRush /Java блог /Java Developer /Анонимные классы в Java
Автор
John Selawsky
Senior Java-разработчик и преподаватель в LearningTree

Анонимные классы в Java

Статья из группы Java Developer
Привет! На сегодняшнем занятии мы продолжим рассматривать тему вложенных классов. Пришла очередь последней группы — анонимных внутренних классов в 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("Мониторинг отслеживания ошибок стартовал!");
   }

};
Напоследок могу порекомендовать тебе отличное видео по теме анонимных классов, где данная тема объясняется максимально просто и понятно :)
А наше сегодняшнее занятие подошло к концу! И хотя мы разобрали последнюю группу вложенных классов, с этой темой мы еще не закончили. Что же мы будем изучать по вложенным классам дальше? Скоро ты обязательно узнаешь! :)
Комментарии (75)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Максим Li Уровень 36
1 апреля 2024
Замечательно
1 марта 2024
Perfect
Anonymous #3380648 Уровень 26
6 января 2024
Серия статей (4) о вложенных классах: 1. Нестатические. Вложенные внутренние классы или Inner Class в Java: https://javarush.com/groups/posts/2181-vlozhennihe-vnutrennie-klassih 2. Нестатические. Внутренние классы в локальном методе (Method local inner classes): https://javarush.com/groups/posts/2190-vnutrennie-klassih-v-lokaljhnom-metode 3. Нестатические. Анонимные классы в Java (Anonymous Inner Class): https://javarush.com/groups/posts/2193-anonimnihe-klassih 4. Статические. Статические вложенные классы (Static Nested Classes): https://javarush.com/groups/posts/2183-staticheskie-vlozhennihe-klassih
Андрей Уровень 42
28 декабря 2023
неудачный какой-то пример
28 ноября 2023


В конце стоит точка с запятой! Она стоит там не просто так. Мы одновременно объявляем класс (посредством фигурных скобок) и создаем его объект с помощью (); объясните почему в коде стоит ";" после фигурой скобки в пояснении после "()"? Это ошибка?
Alexander Rozenberg Уровень 32
26 июля 2023
fine
Anonymous #??? Уровень 6
29 июня 2023

MonitoringSystem errorModule = new MonitoringSystem() {
   //ошибка! Inner classes cannot have static declarations!
   static int staticInt = 10;
   @Override
   public void startMonitoring() {
       System.out.println("Мониторинг отслеживания ошибок стартовал!");
   }
};
У меня в идее все декларирует(компиляторо не ругается) я декларировал и переменные статические и создавал статические методы, другой вопрос что статику из анонимного класса не достать. Почему автор так написал? Может я что то не так понял? Есть ответы: Внутренний анонимный класс может содержать статические переменные и методы c версии Java 16! Статические методы или переменные достать можно, просто нужно обратиться к ним в main методе внутри воложеного класса: System.out.println(staticInt); Иначе как сделать извлечение статик элементов из анонимного класса я не придумал.
No Name Уровень 32
23 июня 2023
+ статья в копилке
19 июня 2023
Предыдущая статья про нестатические вложенные классы (1/3 часть): https://javarush.com/groups/posts/2181-vlozhennihe-vnutrennie-klassih Продолжение про статические вложенные классы (3/3 часть): https://javarush.com/groups/posts/2183-staticheskie-vlozhennihe-klassih
16 мая 2023
Спасибо, понравилось!