Лямбда-выражения и функциональные интерфейсы в Java
Что это такое, как функционирует и в каких целях используется?
Содержание:
Основы Python: бесплатный курс для всех уровней навыков Создание портфолио: 4 увлекательных проекта и интерактивное обучение Изучите возможности курса: что вас ждет в процессе обучения
Узнать большеСоздадим функции, которые будут вычислять сумму и произведение двух чисел:
Теперь давайте создадим единый метод, назовем его processTwoNumbers. Этот метод будет принимать два числовых параметра и код для их обработки.
private int processTwoNumbers(int a, int b, [код передаётся сюда])
При использовании метода sum третий параметр будет принимать в качестве аргумента операцию a+b, тогда как для метода mult этот параметр примет значение a*b.
Стоит отметить, что в качестве третьего аргумента можно использовать лишь код, который принимает два параметра определенного типа (в нашем случае это int) и выдает результат также нужного типа — int.
Следует каким-то образом уведомить компилятор о том, что необходимо ограничить будущих программистов от передачи некорректного кода (например, a+b+c).
В этом случае нам понадобится сигнатура метода, которая будет выступать в роли третьего параметра для нашего метода processTwoNumbers.
private int computeSumOfTwoIntegers(int a, int b, [method signature])
Однако как можно добавить третий параметр, не делая сигнатуру метода processTwoNumbers слишком громоздкой и трудночитаемой? Разработчики Java нашли элегантное решение этой задачи. Они разработали концепцию функциональных интерфейсов.
Определение функционального интерфейса: ключевые аспекты
Функциональным интерфейсом называют такой интерфейс, который включает в себя всего один абстрактный метод, представляющий собой описание метода без его реализации. При этом статические методы и методы с реализацией по умолчанию не учитываются, и их количество в функциональном интерфейсе может быть любым.
Если метод принимает в качестве параметра функциональный интерфейс, то при его вызове необходимо передать блок кода в качестве одного из аргументов.
Предоставляемый фрагмент кода обязан соответствовать следующему требованию: его сигнатура должна быть идентична сигнатуре единственного абстрактного метода функционального интерфейса.
Чтобы прояснить ситуацию, давайте рассмотрим это на конкретном примере:
В Java существует ряд предопределённых функциональных интерфейсов, которые отличаются как количеством, так и типом параметров для входа и выхода. Примером такого интерфейса является ToIntBiFunction, о котором упоминалось ранее. Когда вы разрабатываете собственный функциональный интерфейс, крайне важно не забыть добавить аннотацию @FunctionalInterface. Эта аннотация служит для того, чтобы компилятор мог подтвердить, что создаваемый интерфейс действительно отвечает критериям функционального интерфейса.
Функциональный интерфейс ToIntBiFunction
- Функция принимает два аргумента (T t, U u), где T и U обозначают возможность использования параметров различных типов. К примеру, это могут быть Long и String. Однако в нашем случае это излишне, поскольку оба аргумента имеют один и тот же тип — int.
- возвращает результат в виде целого числа.
Вот что получится:
Интерфейс ToIntBiFunction
Для того чтобы реализовать переданный код в рамках метода processTwoNumbers, необходимо осуществить вызов метода из функционального интерфейса.
Теперь мы подошли к теме лямбда-выражений.
Понимание лямбда-выражений: основные концепции
Это сжатая форма записи, вдохновленная λ-исчислением, предназначенная для передачи фрагментов кода в качестве аргументов в другую программу.
В сущности, это класс или метод, не имеющий имени. Поскольку в Java, за исключением примитивных типов, все является объектами, лямбда-выражения также должны ассоциироваться с определённым объектным типом. Этот тип, как вы уже могли предположить, называется функциональным интерфейсом.
Таким образом, лямбда-выражение не является самостоятельным элементом, а служит для реализации метода, который задан в функциональном интерфейсе.
Если бы не было лямбд, каждый раз вызывание метода processTwoNumbers потребовало бы следующего подхода:
Важно отметить, что в приведённом примере biFunction реализован при помощи анонимных классов. В противном случае нам пришлось бы создавать отдельный класс, который реализует интерфейс ToIntBiFunction и включает в себя метод applyAsInt. Используя анонимный класс, мы смогли выполнить эту задачу быстро и эффективно, не прибегая к созданию дополнительных классов.
В приведённом примере все элементы, за исключением одной строки, избыточны. Функциональную нагрузку несёт лишь выражение return a + b, в то время как остальная часть кода представляет собой технические излишества. Их количество оказалось довольно значительным, даже для того чтобы просто реализовать операцию сложения двух чисел.
Именно здесь на помощь приходят лямбда-выражения. Благодаря им, процесс создания biFunction значительно упрощается и может быть выполнен всего за десять символов!
Лямбда-выражение имеет следующую структуру: (аргументы) -> (тело функции)
Наша лямбда примет следующий вид:
Би-функция, принимающая два целых числа и возвращающая их сумму, может быть определена следующим образом: ToIntBiFunction
И вот, этот набор из 10 символов можно использовать в качестве аргумента для метода, который принимает функциональный интерфейс в качестве параметра. При этом, как правило, обходятся без создания промежуточной переменной и передают лямбду непосредственно.
Компилятор удостоверится, что лямбда-выражение соответствует требованиям функционального интерфейса, то есть принимает необходимое количество аргументов нужных типов. В данном случае мы используем функциональный интерфейс ToIntBiFunction. Его единственный абстрактный метод имеет сигнатуру, в которой указаны два параметра: (Integer a, Integer b).
К примеру, если вызвать метод таким образом, код не пройдет компиляцию, так как будет использован только один аргумент:
Существует множество способов записи лямбд. Мы изучили лишь один из них.
Применение лямбда-выражений в различных сферах.
В различных ситуациях. Одним из распространённых примеров является итерация по элементам с использованием цикла:
Лямбды также находят применение в компараторах при осуществлении сортировки. Рассмотрим ситуацию, когда требуется упорядочить коллекцию на основе последней буквы каждого слова:
В большинстве случаев при использовании коллекций в сочетании со Stream API трудно обойтись без лямбда-выражений. В следующем примере мы сначала фильтруем поток данных на основе определенного значения (с помощью метода filter), затем преобразуем каждый элемент (метод map) и, наконец, собираем результаты в список (метод collect):