Пользователь Professor Hans Noodles
Professor Hans Noodles
41 уровень

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

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

};
Напоследок могу порекомендовать тебе отличное видео по теме анонимных классов, где данная тема объясняется максимально просто и понятно :)
А наше сегодняшнее занятие подошло к концу! И хотя мы разобрали последнюю группу вложенных классов, с этой темой мы еще не закончили. Что же мы будем изучать по вложенным классам дальше? Скоро ты обязательно узнаешь! :)
Комментарии (29)
Чтобы просмотреть все комментарии или оставить свой,
перейдите в полную версию
Future Man 25 уровень
14 февраля 2021
т.е. создавая объект типа SomeInterface мы создём объект класса без названия, который импелементит SomeInterface и тут переопределяем его методы.. вроде понятно, хотя первое что ударило в глаза и мозг, это объект ТИПА ИНТЕРФЕЙСА, и сразу откуда то то из глубины - ЭТО ЖЕ невАзможА!!! ))) я же помню что нельзя создавать объекты от интрефейсов )))
Сергей Волков 28 уровень, Киев
21 января 2021
Из примера выше: Так как MonitoringSystem - функциональный интерфейс (интерфейс с одним абстрактным методом) и нам нужно описать анонимный класс только ля того чтобы реализовать этот метод, то в таком случае можно использовать лямбда выражение (это основное нововведение в Java 8). На JavaSyntaxPro есть отличный курс по лямбда. Советую к прочтению. https://javarush.ru/quests/lectures?quest=QUEST_JAVA_SYNTAX_PRO&level=18
Pig Man 41 уровень
2 декабря 2020
И зачем?

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

   public void someSpecificMethod() {
       System.out.println("Специфический метод только для первого модуля");
   }
};
У нас ссылка типа MonitoringSystem, которая понятия не имеет ни о каком someSpecificMethod(), только о startMonitoring(). Мы не сможем написать:

generalModule.someSpecificMethod();
Только:

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

   public void someSpecificMethod() {
       System.out.println("Специфический метод только для первого модуля");
   }
}.someSpecificMethod();
Либо использовать добавочные методы в переопределенных:

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

   public void someSpecificMethod() {
       System.out.println("Специфический метод только для первого модуля");
   }
};

generalModule.startMonitoring();
ERGAN 22 уровень
16 октября 2020
Я так понимаю, что можно так же создавать анонимные классы из абстрактных классов? Только жаль в статье про это не сказано.
Alukard 36 уровень, London Expert
9 октября 2020
Спасибки 😇
Сергей 25 уровень, Самара
25 июля 2020
а зачем они нужны?их только один объект можно создать?
Vad 36 уровень Expert
15 июля 2020
В первом исходном коде кажется не хватает интерфейса : public interface MonitoringSystem { public void startMonitoring(); } По крайней мере, у меня без него код не работает.))) Либо надо создавать класс и объявлять в нём нужные методы. А при создании объектов в main только переопределять их: public class MonitoringSystem { void startMonitoring() {} void someSpecificMethod() {} } P.S. Вроде что-то начинаю понимать: Класс MonitoringSystem по-любому надо где-то объявлять, т.к. он является суперклассом для наших анонимных классов...
Makim Adamenko 1 уровень, Оренбург
9 июля 2020
бля совсем забыл 😭😢😤😤 я же не выключил вилку щас весь дом в берюзовый поерасит
Андрей 18 уровень Expert
8 июля 2020
"Если мы не хотим создавать целый класс для того чтобы реализовать один метод" © Наиль А в чём проблема создать класс? Экономим пять строк кода?
Eduard Kingster Yeremeev 0 уровень
27 мая 2020
Спасибо, очень хорошее объяснение.