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, переопределили его тремя разными способами и вызвали трижды. Анонимные классы - 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("Мониторинг отслеживания ошибок стартовал!");
   }

};
Напоследок могу порекомендовать тебе отличное видео по теме анонимных классов, где данная тема объясняется максимально просто и понятно :)
А наше сегодняшнее занятие подошло к концу! И хотя мы разобрали последнюю группу вложенных классов, с этой темой мы еще не закончили. Что же мы будем изучать по вложенным классам дальше? Скоро ты обязательно узнаешь! :)
Комментарии (58)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Vlad Уровень 26
17 марта 2023
Хорошая лекция, особенно понравилось видео в конце👍
Griboed Уровень 27
20 января 2023
вот кое что не понятно. Тут написано: "Если каждому из наших анонимных классов-модулей понадобится какое-то отличающееся поведение, свои специфические методы, которых нет у других, мы легко можем дописать их:"

MonitoringSystem generalModule = new MonitoringSystem() {

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

   public void someSpecificMethod() {

       System.out.println("Специфический метод только для первого модуля");
   }
};
Вопрос. А имеет смысл такие методы вообще писать (даже если пропустит компилятор)? будет ли вообще хоть какая-нибудь возможность достучаться до специфического метода? Ведь данный метод существует лишь в анонимном классе, имя которого нам не известно. Объект анонимного класса хранится в переменной типа интерфейса. Данному интерфейсу ничего не известно о данном методе (который есть только у наследника - в анонимном классе). И компилятор по этой причине не позволит к нему обратиться. Более того, мы не сможем создать ссылочную переменную типа анонимного класса и перекинуть туда объект, чтобы вызвать данный метод - по причине того, что нам банально не известно имя анонимного класса, чтобы создать ссылочную переменную данного типа. Я прав?
Georgius #2914078 Уровень 26
17 января 2023
Супер лекция. Еще бы компоновали ссылки в конце по смежным темам(здесь например по всем типам вложенных классов). А то искать через поисковик неудобно. Или я чего-то не догоняю?..
Art Sch Уровень 1
11 января 2023

import java.util.Scanner;
interface Eatable{
    public void eat();
}
public class Test {
    public static final Scanner menu = new Scanner(System.in);
    public static final String whatWouldULikeToEat = menu.nextLine();
    public static void main(String[] args) {
       Eatable eatable = new Eatable() {
           @Override
           public void eat() {
               System.out.printf("Well, I would like to eat %s, please.", whatWouldULikeToEat);
           }
           public String receipt(){
               return whatWouldULikeToEat;
           }
       };
       eatable.eat();
        System.out.println("\nOkay, sir.");
    }
}

Момент "В культурном англоязычном ресторане" хд)
vasya_kesegach Уровень 0
3 сентября 2022
Важный момент в том, что дополнительные методы, помимо тех, что мы переопределяем, нельзя использовать извне этого анонимного класса, а только внутри него. Т.е. в примере:

MonitoringSystem generalModule = new MonitoringSystem() {

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

   public void someSpecificMethod() {

       System.out.println("Специфический метод только для первого модуля");
   }
};
someSpecificMethod() нельзя будет вызвать где-то ещё, кроме как внутри этого класса
Dimon Уровень 27
30 июля 2022
За видео отдельное спасибо!
Alexey Svorkin Уровень 37
12 июня 2022
static int staticInt = 10; Уверенны, что статическую переменную нельзя создавать?!??!?
Мирослав Уровень 28
8 мая 2022
Поигрался )))

public class Test {
    private static final String cow = "Cow";

    public static void main(String[] args) {

        AnimalActions eatAction = new AnimalActions() {
            @Override
            public void eat() {
                System.out.printf("Animal %s eating...", getAnimal());
            }

            public String getAnimal() {
                return cow;
            }
        };
        eatAction.eat();
    }
}

interface AnimalActions {
    void eat();
}
Дмитрий Уровень 37
2 апреля 2022
Видос - бомба
Druha Уровень 31
4 декабря 2021
данная статья отлично подошла бы к теме введение в лямбду