«1. Введение

В этом руководстве мы рассмотрим анонимные классы в Java.

Мы опишем, как мы можем объявлять и создавать их экземпляры. Мы также кратко обсудим их свойства и ограничения.

2. Объявление анонимного класса

Анонимные классы — это внутренние классы без имени. Поскольку у них нет имени, мы не можем использовать их для создания экземпляров анонимных классов. В результате нам приходится объявлять и создавать экземпляры анонимных классов в одном выражении в момент использования.

Мы можем либо расширить существующий класс, либо реализовать интерфейс.

2.1. Расширение класса

Когда мы создаем экземпляр анонимного класса из существующего, мы используем следующий синтаксис:

В скобках мы указываем параметры, которые требуются конструктору класса, который мы расширяем: ~~ ~

new Book("Design Patterns") {
    @Override
    public String description() {
        return "Famous GoF book.";
    }
}

Естественно, если конструктор родительского класса не принимает аргументов, мы должны оставить круглые скобки пустыми.

2.2. Реализация интерфейса

Мы также можем создать экземпляр анонимного класса из интерфейса:

Очевидно, интерфейсы Java не имеют конструкторов, поэтому круглые скобки всегда остаются пустыми. Это единственный способ реализовать методы интерфейса:

new Runnable() {
    @Override
    public void run() {
        ...
    }
}

После того, как мы создали экземпляр анонимного класса, мы можем присвоить этот экземпляр переменной, чтобы иметь возможность сослаться на него позже.

Мы можем сделать это, используя стандартный синтаксис для выражений Java:

Runnable action = new Runnable() {
    @Override
    public void run() {
        ...
    }
};

Как мы уже упоминали, объявление анонимного класса является выражением, следовательно, оно должно быть частью оператора. Это объясняет, почему мы поставили точку с запятой в конце оператора.

Очевидно, что мы можем избежать присваивания экземпляра переменной, если создадим этот экземпляр встроенным: () занимает много места.

List<Runnable> actions = new ArrayList<Runnable>();
actions.add(new Runnable() {
    @Override
    public void run() {
        ...
    }
});

3. Свойства анонимных классов

Существуют определенные особенности использования анонимных классов по сравнению с обычными классами верхнего уровня. Здесь мы кратко коснемся наиболее практических вопросов. Для получения наиболее точной и обновленной информации мы всегда можем обратиться к Спецификации языка Java.

3.1. Конструктор

Синтаксис анонимных классов не позволяет реализовать несколько интерфейсов. Во время построения может существовать ровно один экземпляр анонимного класса. Поэтому они никогда не могут быть абстрактными. Поскольку у них нет имени, мы не можем их расширить. По той же причине анонимные классы не могут иметь явно объявленных конструкторов.

На самом деле отсутствие конструктора не представляет для нас проблемы по следующим причинам:

3.2. Статические члены

  1. we create anonymous class instances at the same moment as we declare them
  2. from anonymous class instances, we can access local variables and enclosing class’s members

Анонимные классы не могут иметь никаких статических членов, кроме тех, которые являются постоянными.

Например, это не скомпилируется:

Вместо этого мы получим следующую ошибку:

new Runnable() {
    static final int x = 0;
    static int y = 0; // compilation error!

    @Override
    public void run() {...}
};

3.3. Область действия переменных

The field y cannot be declared static in a non-static inner type, unless initialized with a constant expression

Анонимные классы захватывают локальные переменные, находящиеся в области действия блока, в котором мы объявили класс:

Как видим, количество локальных переменных и действие определяются в одном блоке. По этой причине мы можем получить доступ к count из объявления класса.

int count = 1;
Runnable action = new Runnable() {
    @Override
    public void run() {
        System.out.println("Runnable with captured variables: " + count);
    }           
};

Обратите внимание, что для того, чтобы можно было использовать локальные переменные, они должны быть окончательными. Начиная с JDK 8 больше не требуется объявлять переменные с ключевым словом final. Тем не менее, эти переменные должны быть окончательными. Иначе получим ошибку компиляции:

Для того, чтобы компилятор решил, что переменная, по сути, неизменяемая, в коде должно быть только одно место, в котором мы присваиваем ей значение. Мы могли бы найти больше информации об эффективно окончательных переменных в нашей статье «Почему локальные переменные, используемые в лямбда-выражениях, должны быть окончательными или эффективно окончательными?» членов окружающего его класса.

[ERROR] local variables referenced from an inner class must be final or effectively final

4. Примеры использования анонимных классов

«Может быть большое разнообразие приложений анонимных классов. Давайте рассмотрим некоторые возможные варианты использования.

4.1. Иерархия классов и инкапсуляция

Мы должны использовать внутренние классы в общих случаях использования и анонимные в очень специфических, чтобы добиться более чистой иерархии классов в нашем приложении. При использовании внутренних классов мы можем добиться более тонкой инкапсуляции данных включающего класса. Если мы определим функциональность внутреннего класса в классе верхнего уровня, то включающий класс должен иметь общедоступную или пакетную видимость некоторых своих членов. Естественно, бывают ситуации, когда это не очень ценится или даже принимается.

4.2. Более чистая структура проекта

Мы обычно используем анонимные классы, когда нам нужно изменить на лету реализацию методов некоторых классов. В этом случае мы можем избежать добавления в проект новых файлов *.java для определения классов верхнего уровня. Это особенно верно, если этот класс верхнего уровня будет использоваться только один раз.

4.3. Слушатели событий пользовательского интерфейса

В приложениях с графическим интерфейсом наиболее распространенным вариантом использования анонимных классов является создание различных прослушивателей событий. Например, в следующем фрагменте:

мы создаем экземпляр анонимного класса, реализующего интерфейс ActionListener. Его метод actionPerformed срабатывает, когда пользователь нажимает кнопку.

Начиная с Java 8, лямбда-выражения кажутся более предпочтительными.

button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        ...
    }
}

5. Общая картина

Анонимные классы, которые мы рассмотрели выше, являются лишь частным случаем вложенных классов. Как правило, вложенный класс — это класс, объявленный внутри другого класса или интерфейса:

Глядя на диаграмму, мы видим, что анонимные классы вместе с локальными и нестатическими членами образуют так называемые внутренние классы. Вместе со статическими классами-членами они образуют вложенные классы.

6. Заключение

В этой статье мы рассмотрели различные аспекты анонимных классов Java. Мы также описали общую иерархию вложенных классов.

Как всегда, полный код доступен в нашем репозитории GitHub.

«

As always, the complete code is available over in our GitHub repository.