JavaRush /Блог /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("Мониторинг отслеживания ошибок стартовал!");
   }

};
Напоследок могу порекомендовать тебе отличное видео по теме анонимных классов, где данная тема объясняется максимально просто и понятно :)
А наше сегодняшнее занятие подошло к концу! И хотя мы разобрали последнюю группу вложенных классов, с этой темой мы еще не закончили. Что же мы будем изучать по вложенным классам дальше? Скоро ты обязательно узнаешь! :)
Комментарии (83)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Private Joker Уровень 36
4 июля 2024
Лекция кривая, код из лекции не рабочий. Слава Кисусию, что есть GPT: В вашем коде есть несколько проблем, которые могут вызывать ошибки. Давайте рассмотрим их по порядку и исправим. Отсутствие интерфейса или абстрактного класса MonitoringSystem. Поскольку вы создаете анонимный класс на основе MonitoringSystem, он должен быть либо интерфейсом, либо абстрактным классом. Давайте предположим, что это интерфейс. Отсутствие использования объекта errorModule. После создания объекта errorModule его методы нигде не вызываются. Давайте добавим вызов методов. Ошибки доступа к переменной currentErrorsCount. Анонимный класс не имеет доступа к нестатическим переменным внешнего класса, если они не являются final или static.
Aman Efer Уровень 39
14 июня 2024
Внутренние классы, в т.ч локальные и анонимные, начиная с jdk16, могут иметь статические члены (переменные и методы).
Kirill Уровень 41
14 мая 2024
Если каждому из наших анонимных классов-модулей понадобится какое-то отличающееся поведение, свои специфические методы, которых нет у других, мы легко можем дописать их: Вот только вызвать мы их не сможем, потому что эти "специфические методы" не объявлены в интерфейсе. Ну точнее можем конечно... Танцуя с бубном через рефлексию :)))
Kirill Уровень 41
14 мая 2024
И для такой небольшой работы мы написали целую систему: 3 класса и один интерфейс! И все это — ради 6 строк кода. Так а что, собственно, в итоге поменялось? Мы получили все те же 1 интерфейс и 3 реализующих его анонимных класса :))) С точки зрения проектирования системы, как раз логичней было бы объявить все 3 класса как приватные статические вложенные где-нибудь в самом конце объявления класса, сделав их при этом синглтонами - так и код наглядней, и не нужно городить простыню из 3 анонимных классов.
Максим Li Уровень 40
1 апреля 2024
Замечательно
1 марта 2024
Perfect
Anonymous #3380648 Уровень 30
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
Андрей Уровень 51
28 декабря 2023
неудачный какой-то пример
28 ноября 2023


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