Модуль 8. Урок 3. Анонимные классы в Java.

Для сохранения прогресса вступите в курс. Войти или зарегистрироваться.

Анонимные классы объявляются без указания в коде имени класса.

Анонимные классы могут быть созданы:

1) как реализация интерфейса

public class Foo {
    // Анонимный класс, который реализует интерфейс SayHello
    static SayHello h = new SayHello() {
        @Override
        public void say() {
            System.out.println("Метод внутреннего анонимного класса");
        }
    };

    public static void main(String[] args) {
        h.say();
    }
}
// somewhere 
interface SayHello {
    void say();
}

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

2) как наследник определённого класса

public class External {
    // Анонимный класс наследуется от класса Foo
    static Foo foo = new Foo() {
        @Override
        public void show() {
            super.show();
            System.out.println("Метод внутреннего анонимного класса");
        }
    };
    public static void main(String[] args) {
        foo.show();
    }
}

class Foo {
    public void show() {
        System.out.println("Метод суперкласса");
    }
}

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

Если анонимный класс объявлен в статическом блоке:

import java.util.function.Consumer;

public class Anonymous {
    public static void main(String[] args) {

        Consumer<String> foo = new Consumer() {
            @Override
            public void accept(Object o) {
                System.out.println(o + " TODO something useful!");
            }
        };

        foo.accept("Работа анонимного класса в статическом методе.");
    }
}

То его второй экземпляр можно создать так: Consumer otherInstance = foo.getClass().newInstance(); (эту строку нужно завернуть в try-catch или пробросить исключения выше). И переиспользуем ту же логику, но уже в другом инстансе: otherInstance.accept("Работа второго экземпляра анонимного класса в статическом методе.");.

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

Вот как это выглядит в коде:

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.function.Consumer;

public class Anonymous {

    public void nonStaticMethod() {
        // собственно наш анонимный класс
        Consumer<String> foo = new Consumer() {
            @Override
            public void accept(Object o) {
                System.out.println(o + " TODO something useful!");
            }
        };

        foo.accept("Работа анонимного класса в НЕстатическом методе.");

        // достаем конструктор у анонимного класса
        Constructor[] constructors = foo.getClass().getDeclaredConstructors();
        // Записываем ссылку на объект Anonymous в массив параметров, для конструктора.
        Object[] params = new Object[1];
        params[0] = this;

        // Создаем новую ссылку типа Consumer
        Consumer otherInstance = null;
        try {
            // создаем новый экземпляр анонимного класса, передав в его конструктор массив параметров, ссылку на текущий
            // класс, в котором мы и осуществляем все манипуляции с анонимным классом.
            otherInstance = (Consumer) constructors[0].newInstance(params);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        otherInstance.accept("Работа второго экземпляра анонимного класса в НЕстатическом методе.");
    }

    public static void main(String[] args) {
        Anonymous anon = new Anonymous();
        anon.nonStaticMethod();
    }
}

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

Анонимные классы полезны в некоторых "узких" участках кода, когда нет необходимости их потом переиспользовать где-то еще. Чаще всего на практике используют лишь один экземпляр анонимного класса. Если же реализацию интерфейса придется использовать много раз в коде — то лучше использовать лямбда-выражения (лямбды), которые стали доступны начиная с JAVA 8.

Например, реализацию интерфейса Consumer, в текущем уроке, можно переписать с помощью лямбд:

import java.util.function.Consumer;

public class Anonymous {

    public static void main(String[] args) {
        Consumer<String> foo = o -> System.out.println(o + " TODO something useful!");
        foo.accept(" Using lambda :)");
    }
}

Преимущества и недостатки между анонимными классами и лямбдами постепенно меняются в сторону лямбд. Об этих различиях можно почитать в статье на Хабре.

Отдельно стоит упомянуть, что не все версии Android поддерживают лямбды. И многим будет проще использовать анонимные классы, чем прикручивать лямбды.

Группы для общения:

Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Javascript, PHP, Python и Java.

Хекслет

Подробнее о том, почему наше обучение работает →