Программирование • 06 августа 2024 • 5 мин чтения

Лямбда-функции в Java: что это и зачем использовать их в коде

Разбираемся, что представляют собой lambda-функции (часто их называют «lambda-выражения» или просто «лямбда»), какими они бывают и как использовать их в Java. Рассказываем о плюсах и минусах, а также приводим примеры лямбда-функций.

Что такое лямбда-выражения

Концепция лямбда-выражений (lambda) появилась в Java с версии 1.8 и ввела в этот язык в элементы функционального программирования. Изначально Java построен на императивном подходе, в рамках которого разработчик описывает конкретные действия, которые должна выполнить программа. А суть функционального подхода состоит в описании не процесса, а результата, который необходимо получить. Результат выводится в виде функций, но при этом они необязательно выполняются сразу, а вычисляются при необходимости.

Лямбда-выражение — одно из понятий функционального программирования. Такие выражения также иногда называют функциями первого порядка или анонимными функциями. Лямбда-выражение представляет собой блок кода, запоминающий контекст вокруг себя в момент создания. Если раньше в коде программист присвоил определённые значения переменным a и b, lambda их помнит и использует для вычислений, к примеру для определения гипотенузы по конкретным значениям катетов a и b.

Обычные функции в языке «Джава» описываются в классах как методы и всегда используют внутри себя только те значения, которые разработчик запишет в экземпляр этого класса. Например, если в классе добавить определённые значения переменной, метод будет работать именно с ними. А лямбда-выражение помнит только те значения, с которыми оно было создано. Допустим, в методе есть переменная и лямбда-выражения в цикле. Каждая новая lambda будет помнить новое значение.

Синтаксис лямбда-функции в Java состоит из аргументов и lambda-выражений. Шаблон лямбда-функции выглядит так:

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

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

double getPiValue() {
return 3.1415;
}

Так его можно записать lambda-выражением:

() -> 3.1415

Типы лямбда-функций

Из коробки, то есть по умолчанию, Java поддерживает два варианта lambda:

1. Одиночное (single-expression body). Включает в себя только единственное выражение и применяется при передаче функции в качестве аргумента к методу. Допустим, она пригодится, если нужно рассчитать сумму:

(x) -> x + 1.

2. Блок кода (multi-statement body). Его также называют многострочным выражением. Это lambda с несколькими операторами, которая даёт возможность выполнять разные операции внутри одного интерфейса. Допустим, оно пригодится для поиска максимального простого числа в группе:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.filter(x -> {
if (n <= 1) {
return false;
}

for (int i = 2; i <= Math.sqrt(n); i++) {
if (n % i == 0) {
return false;
}
}
return true;

.max((x, y) -> x.compareTo(y))
.ifPresent(System.out::println);

Использование лямбда-функций

Lambda-функции применяются в Java в нескольких направлениях:

Функциональные интерфейсы. Лямбды полезны для создания интерфейсов с одним абстрактным методом. В результате есть возможность создавать анонимные классы без необходимости определения нового класса. Раньше в качестве аналога лямбд в Java применялись анонимные классы, теперь это не требуется.

Function<Double, Long> roundFn = d -> Math.round(d)

Stream API. Он представляет собой группу классов и интерфейсов, с помощью которых можно работать с коллекциями объектов. По сути, Stream API основан на lambda-выражениях — к примеру, их используют как условия или функции отображения. Допустим, с помощью lambda можно вывести чётные числа:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.filter(x -> x % 2 == 0)
.forEach(System.out::println);

Асинхронное программирование в SpringBoot. За счёт того, что lambda запоминает контекст, запросы обрабатываются быстрее, освобождая потоки. К примеру, запросы futures, future, listenable future, completable future пишутся в лямбда-выражениях.

Научиться использовать lambda и другие функции в коде можно на курсе «Java-разработчик». Во время обучения студенты осваивают Java Core и фреймворк Spring, учатся работать с алгоритмами, структурами и базами данных, изучают Unit- и Mock-тестирование. По итогам курса можно собрать портфолио и найти первую работу по специальности.

Преимущества и недостатки использования

Рассмотрим плюсы и минусы лямбда-выражений в программировании на Java.


Преимущества


Недостатки

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

Единая парадигма бэкенда и фронтенда. Асинхронное программирование и lambda давно используются при фронтенд-разработке, а благодаря обновлению Java они применяются и в бэкенде. Это делает работу над программами более гармоничной.

Упрощение изучения других языков программирования. Освоив лямбда-выражения в Java, разработчик может перейти к изучению языков следующего уровня сложности, например Scala и Clojure.

Повышение производительности. Применение StreamAPI и лямбда-выражений может улучшить производительность программы. Например, за счёт предварительной фильтрации и отсутствия лишних проходов в цикле выражение:
stream().filter(???).forEach(...)
будет эффективнее, чем цикл:
for(...) {
if(???) {...

Трудность перехода. Поскольку в целом Java использует императивный подход, при изучении lambda начинающим разработчикам бывает сложно перестроиться на функциональный подход. Например, привыкнуть, что в выражение нужно передавать только неизменяемые переменные.

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

Сложность написания. Продвинутая работа с лямбда-выражениями требует от программиста глубоких знаний языка «Джава» и математики.

Примеры lambda-функций

Возьмём для примера for-each — цикл в Java, помогающий в работе с коллекциями. Это типичный Consumer — интерфейс, принимающий параметр, но не возвращающий результат. Если в цикле нужно вывести элементы списка на экран, пригодится lambda-выражение.

List<String> trees = Arrays.asList("Дуб", "Берёза", "Тополь");
trees.forEach(tree -> System.out.println(tree));

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

Другой пример — Producer, функция в Stream API, которая не принимает никакого значения, но возвращает какой-то результат. Её также называют генераторной функцией. Допустим, поток должен сгенерировать число 42 и вывести десять элементов на экран. В решении этой задачи поможет лямбда-выражение:

IntStream stream = Stream.generate(() -> 42).limit(10);
stream.forEach(System.out::println);

Ещё lambda в Java используют в интерфейсе Function. Он может принимать и выдавать значение, но разного типа. К примеру, Function<Integer, String> принимает цифру, а возвращает слово, соответствующее этой цифре. Эта функция переведёт любое число меньше 100 в его прописную форму:

public static void main(String[] args) {
//используем словари русских цифр и чисел в специальной lambda-функции
Function<Integer, String> numberToWords = number ->
numberWordsMap.containsKey(number) ? numberWordsMap.get(number) :
tensWordsMap.containsKey(number) ? tensWordsMap.get(number) :
tensWordsMap.get((number / 10) * 10) + " " + numberWordsMap.get(number % 10);

int number = 21;
System.out.println(numberToWords.apply(number)); // На выходе получим: двадцать один
}

Кроме того, лямбда-выражения пригодятся при реализации интерфейса Predicate, который возвращает true или false в ответ на значение. Он применяется для фильтрации данных из потока. Допустим, можно отфильтровать строку так, чтобы она начиналась с элемента на определённую букву.

List<String> trees = Arrays.asList("Дуб", "Берёза", "Тополь");
trees.stream()
.filter(tree -> tree.startsWith("Т"))
.forEach(System.out::println);

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

Совет эксперта

Пётр Кушнир
Применение лямбда-функций переведёт ваши алгоритмы на следующий уровень развития, они станут более выразительными и лаконичными, а встроенные механизмы Stream API и асинхронной обработки запросов сделают ваши программы более оптимальными и производительными. Но это потребует от вас перестройки своего привычного мышления на новый функциональный стиль.
Статью подготовили:
Пётр Кушнир
Яндекс Практикум
Автор курса «Java-разработчик»
Женя Соловьёва
Яндекс Практикум
Редактор
Анастасия Павлова
Яндекс Практикум
Иллюстратор

Дайджест блога: ежемесячная подборка лучших статей от редакции

Поделиться

Успейте начать учебу в Практикуме до конца ноября со скидкой 20%

Wed Aug 14 2024 16:35:49 GMT+0300 (Moscow Standard Time)