Лямбда выражения в Java. Сравнение подходов решения задач.

lambda

В версию Java 8 была добавлена полноценная поддержка лямбда-выражений — специализированного синтаксиса, позволяющего определить функциональные объекты из формальной системы исчислений λ.

Являясь упрощенным вариантом записи безымянных классов, лямбда в Java дает возможность писать более аккуратный и лаконичный программный код для библиотеки Stream API и утилитарного класса Optional. Помимо стандартных коллекций классов, лямбда выражения Java могут использоваться сторонними API: реактивные стримы, JavaFX и т.д.

Определение функциональных интерфейсов и лямбда-выражений

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

Функциональный интерфейс, встроенный в язык программирования Java является программным интерфейсом, в котором объявляется исключительно один абстрактный метод. При этом интерфейс может содержать определенное количество дополнительных методов default, например — java.util.function.Function.

Подобный интерфейс нужно отмечать программной аннотацией @FunctionalInterface. Поскольку виртуальная машина Java (JVM) учитывает любой интерфейс, содержащий один абстрактный метод в качестве функционального, данное условие не является обязательным.

Пример простого интерфейса функционального типа:

@FunctionalInterface
public interface RentSearcher {

    boolean applyFilter(House house);
}

Структурные особенности лямбда-выражений

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

Основным отличием сигнатуры выражения лямбда Java от сигнатуры используемого метода является наличие только двух структурных частей: тела, разделенного с помощью оператора «->» и списка использованных аргументов. Все аргументы и возвращаемый тип берутся из интерфейса. Также интерфейс может описывать выбрасываемые исключения.

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

Типы указываются сразу для всех имеющихся аргументов или не указываются вовсе. Данное правило также относится к использованию ключевого слова var, которое было добавлено в Java 11.

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

Вышеописанные особенности также применимы для тела, используемого в Java лямбда. Если оно состоит из одной отдельной строки, точка с запятой, оператор return и фигурные скобки можно опустить. Телом лямбда-выражения может выступать значение method reference — ссылка на метод.

Пример создания лямбда-выражения

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

квартиры

При использовании анонимного класса, процесс создания объекта будет выглядеть следующим образом:

RentSearcher rentSearcher = new RentSearcher() {
    public boolean applyFilter(House house) {
        return house.getPrice() >= 7500;
    }
};

При этом объект RentSearcher можно описать с помощью лямбда-выражения, что позволит сделать запись аккуратнее:

RentSearcher rentSearcher = (House house) -> {
    return house.getPrice() >= 7500;
};

С помощью простых действий, элемент кода можно сделать еще аккуратнее, тем самым повысив качество кода:

RentSearcher rentSearcher = house -> house.getPrice() >= 7500;

// Folowing form can be used for JDK11
var rentSearcher = (RentSearcher) house -> house.getPrice() >= 7500;

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

Использование лямбда-выражений

Что касается лямбда выражения Java задачи, следует рассмотреть пример, условия которого предполагают создание метода для вывода из общего списка домов ту квартиру, у которой цена ниже 7500 и это однокомнатная квартира.

Программный код для данной задачи в большинстве случаев будет выглядеть следующим образом:

void printCheapAppartments(List<House> houses) {
    for(House house : houses) {
        if (house.type == House.TYPE_ONE_BEDROOM && house.price <= 7500) {
            System.out.println(house.getDescription());
        }
    }
}

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

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

Для этого логичнее всего применить два функциональных интерфейса: с целью фильтрации объектов использовать java.util.function.Predicate, а для действия к подходящим объектам — java.util.function.Consumer.

Интерфейс java.util.function.Predicate применяется для декларирования основного абстрактного метод, с помощью которого код сначала принимает объект, а после — возвращает ему значение boolean в соответствии с требуемым свойствам.

В данном случае использование java.util.function.Consumer позволяет объявить основной абстрактный метод, принимающего определенный объект и выполняющего с ним те или иные действия, указанные в коде.

public static void printCheapAppartments(List<House> houses, Predicate<House> pd, Consumer<House> cs) {
    for(House house : houses) {
        if (pd.test(house)) {
            cs.accept(house);
        }
    }
}

Исходя из вышеописанных особенностей, используемый изначально метод printCheapAppartments будет выглядеть следующими образом:

printCheapAppartments(houses, new Predicate<House>() {
    @Override
    public boolean test(House house) {
        return house.type != House.TYPE_ONE_BEDROOM && house.price <=9000;
    }
}, new Consumer<House>() {
    @Override
    public void accept(House house) {
        System.out.println(house.getDescription());
    }
});

Первоначальную задачу, основной целью которой является вывод однокомнатных квартир с ценой ниже 7500 из базы данных, можно решить с помощью вызова метода printCheapAppartments с применением лямбда выражений:

printCheapAppartments(houses, house -> house.type == House.TYPE_ONE_BEDROOM && house.price <=7500, house -> System.out.println(house.getDescription()));

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

Безымянные и обычные классы, лямбды

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

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

При этом важно отметить, что большинство ситуаций, в которых есть возможность использования лямбда-выражений (обычно классы CompletableFuture и Optional или программный интерфейс Stream) получаются очень компактными и логичными выражениями.

Related posts

Leave a Comment