10. Анонимные классы, интерфейсы, методы. Лямбда-выражения

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

С практической точки зрения это может выглядеть бесполезным, однако, анонимные объекты требуются довольно часто - обычно в тех ситуациях, когда объект класса используется один раз. Рассмотрим пример:

class PrintManager {
    
    // Различные поля и методы
    
    public void printFile(File file) {
        // Распечатка файла на принтере
    }
}

В данном случае, нам нужен объект класса PrintManager только для одного действия - для распечатки файла. То есть, мы создаем объект, вызываем метод, после чего объект нам больше не нужен.

В таких случаях удобно использовать анонимные объекты.

File file = new File("file.txt");

// Обычное создание объекта и вызов его метода
PrintManager manager = new PrintManager();
manager.printFile(file);

// Использование анонимного объекта
new PrintManager().printFile(file);

// Использование двух анонимных объектов
new PrintManager().printFile(new File("file.txt"));

В первом случае используется обычный порядок работы с объектами - создаем объект класса PrintManager, ссылка на объект записывается в ссылочную переменную manager.

Во втором случае мы создаем анонимный объект. Инструкция

new PrintManager().printFile(file);

выполняется следующим образом. Сначала создается новый объект класса PrintManager, оператор new возвращает ссылку на созданный объект. После чего, мы вызываем метод printFile() с аргументом file.

В третьем случае, мы в качестве аргумента метода printFile() передаем анонимный объект класса File. В этом случае, ссылку на объект класса File будет хранить параметр метода printFile() внутри тела метода.

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

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

Основная особенность - анонимный класс не имеет имени. Анонимный класс может быть подклассом существующего класса или реализацией интерфейса.

Особенности анонимного класса:

  • нет явного конструктора;

  • к анонимному классу невозможно обратиться извне объявляющего его выражения;

  • анонимные классы не могут быть статическими;

  • анонимный класс всегда конечен (final);

  • каждое объявление анонимного класса уникально.

В примере ниже объявлены два анонимный класса, которые являются подклассами PrintManager. Оба анонимных класса являются разными уникальными классами.

PrintManager manager1 = new PrintManager() {
    @Override
    public void printFile(File file) {
        super.printFile(file);
    }
};

PrintManager manager2 = new PrintManager() {
    @Override
    public void printFile(File file) {
        super.printFile(file);
    }
};

Анонимные методы (лямбда-выражения)

Из-за особенностей реализации лямбда-выражений в Java, дальнейшая информация по поводу лямбда-выражений справедлива только для языка Java.

Лямбда-выражение - это анонимный метод, то есть метод без названия. Такой метод выполняется не самостоятельно, а служит для реализации функционального интерфейса. Таким образом, лямбда-выражение приводит к некоторой форме анонимного класса.

Функциональный интерфейс - интерфейс, который содержит один и только один абстрактный метод. Как правило, такой метод определяет предполагаемое назначение интерфейса. Следовательно, функциональный интерфейс представляет единственное действие. К функциональным интерфейсам можно отнести, например, интерфейс ActionListener.

Лямбда-выражения позволяют объявить метод и сразу же использовать его. Это полезно в случаях однократного вызова метода, так как сокращает время на объявление и написание метода без необходимости создавать отдельный класс. Лямбда-выражение в Java имеет следующий синтаксис:

(аргументы) -> (тело выражения)

Лямбда-выражение вносит новый элемент в синтаксис и оператор в язык Java. Этот новый оператор называется лямбда-оператором, или операцией "стрелка" ->. Он разделяет лямбда-выражение на две части. В левой части указываются любые параметры, требующиеся в лямбда-выражении (если параметры не требуются, то они указываются пустым списком). А в правой части находится тело лямбда-выражения, где указываются действия, выполняемые лямбда-выражением. Операция -> буквально означает "становиться" или "переходить".

В Java определены две разновидности тел лямбда-выражений. Одна из них состоит из единственного выражения (одиночное выражение), а другая - из блока кода (блочное выражение).

Одиночное лямбда-выражение.

Для начала рассмотрим самое простое лямбда-выражение. Это будет выражение, которое не принимает никаких параметров, а возвращает константу. Также объявим метод, который аналогичен лямбда-выражению.

() -> 99

public double foo() {
    return 99;
}

Приведем еще один пример уже более полезного выражения

() -> Math.random() * 100

public double foo() {
    return Math.random() * 100;
}

Как вам понятно, лямбда-выражение возвращает псевдослучайное значение, умноженное на 100.

Два первых примера не принимают никаких параметров. Если же вам необходимо передать параметры, они указываются списком в левой части лямбда-оператора.

(a, b) -> a * b

public double foo(int a, int b) {
    return a * b;
}

Тип параметров можно указывать явно, но зачастую в этом нет необходимости, потому что тип параметров выводится.

Блочные лямбда-выражения

Предыдущие примеры содержали в теле выражения лишь одно выражение. Мы уже знаем, что такие лямбда-выражения называются одиночными.

Но ничего вам не мешает создавать блочные лямбда-выражения, которые в теле содержат блок выражений. В таком блоке можно создавать циклы, ветвления - все как в обычном блоке выражений.

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

Лямбда-выражение как реализация интерфейса

Как было упомянуто ранее, в Java 8 было введено понятие функционального интерфейса - интерфейса с одним абстрактным методом. Лямбда-выражение не существует самостоятельно, а реализует абстрактный метод, определенный в функциональном интерфейсе.

Таким образом, лямбда может быть указана в том контексте, в котором определен ее целевой тип. Один из таких контекстов создается в том случае, когда лямбда присваивается ссылке на функциональный интерфейс. К числу других контекстов целевого типа относится инициализация переменных, оператор return и аргументы методов.

Last updated