Источник: Medium
Изучив это руководство, вы научитесь использовать лямбда-выражения и поймете, какие преимущества они предоставляют Java-разработчику.
Лямбда-выражения, представленные в Java 8, ознаменовали собой значительную эволюцию языка программирования Java, поскольку они привнесли в него элемент функционального программирования. В языке Java лямбда-выражения используются для оптимизации определенных шаблонов программирования и упрощения работы над кодом, что позволяет делать его более кратким и читабельным.
До Java 8 решение простых задач, таких как сортировка списка или создание нового потока, требовало написания анонимных внутренних классов, которые могли быть довольно многословными и загроможденными. Например, давайте рассмотрим подход к сортировке списка с использованием анонимного внутреннего класса.
Как выглядел код до Java 8:

Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
});
Этот способ написания кода, хотя и был функциональным, не способствовал читабельности и лаконичности. По мере усложнения Java-приложений среди разработчиков росла потребность в более оптимизированном подходе к решению таких рутинных задач.
Что такое лямбда-выражения?
Лямбда-выражение по сути является анонимной функцией; то есть функция без имени. Она не связана ни с каким классом, как метод, но ее можно передавать, как если бы она был объектом, и выполнять по требованию. Эта особенность делает лямбда-выражения ключевой особенностью функционального программирования. Структура лямбда-выражения проста и состоит из трех частей:- Параметры: Они перечислены в круглых скобках. Например, в (x, y), символы x и y являются параметрами.
- Токен стрелки: Символ -> отделяет параметры от тела выражения.
- Тело (Body): Содержит выражения и инструкции для выполнения. Оно может быть одним выражением или блоком кода.
Collections.sort(list, (s1, s2) -> s1.compareTo(s2));
Как видите, этот пример ясно демонстрирует краткость и ясность, привнесенные лямбда-выражениями.
Зачем нужны лямбда-выражения?
Введение лямбда-выражений были добавлены в Java не просто для улучшения синтаксиса. Это был стратегический шаг со стороны сообщества Java, направленный на то, чтобы приспособиться к развивающейся среде программирования, которая все больше склонялась к парадигмам функционального программирования. Функциональное программирование упрощает параллельную обработку, делает код более читаемым и удобным в обслуживании, а также обеспечивает эффективное выполнение кода, особенно в сценариях, включающих коллекции и потоки.Лямбда-выражения и анонимные внутренние классы
Хотя лямбда-выражения могут показаться похожими на анонимные внутренние классы, они принципиально отличаются. Анонимный внутренний класс может создавать экземпляр любого типа, будь то интерфейс, класс или абстрактный класс. С другой стороны, лямбда-выражения по сути являются экземплярами функциональных интерфейсов — интерфейсов с одним абстрактным методом. Это различие имеет решающее значение, поскольку оно лежит в основе функций функционального программирования, которые лямбда-выражения привносят в Java. Например, интерфейс Runnable, который используется для выполнения кода в отдельном потоке, является отличным примером функционального интерфейса. В мире до Java 8 использование интерфейса Runnable предполагало создание анонимного внутреннего класса. С помощью лямбда-выражений ту же функциональность можно достичь гораздо меньшим количеством кода. До Java 8:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Running in a new thread");
}
}).start();
После появления лямбда-выраженией в Java 8:
new Thread(() -> System.out.println("Running in a new thread")).start();
Как видите, лямбда-выражения представляют собой важный шаг вперед в эволюции Java, позволяя разработчикам писать более гибкий, эффективный и читаемый код. Они прокладывают путь функциональному программированию на Java, которое представляет собой сдвиг парадигмы традиционных объектно-ориентированных корней Java.
Синтаксис и структура лямбда-выражений
Понимание синтаксиса лямбда-выражений
Лямбда-выражения в Java предоставляют ясный и краткий способ реализации экземпляров интерфейсов с одним методом (функциональных интерфейсов). Понимание их синтаксиса имеет решающее значение для использования их возможностей в упрощении кода. Лямбда-выражение состоит из трех основных частей:- Параметры: В скобках указаны входные данные для лямбда-выражения. Тип параметров может быть явно объявлен или выведен компилятором.
- Токен стрелки: Символ ->, также известный как токен стрелки, отделяет параметры от тела лямбда-выражения.
- Тело: Тело лямбда-выражения содержит код, определяющий действие лямбда-выражения. Оно может быть отдельным выражением или блоком кода, заключенным в фигурные скобки {}.
(int a, int b) -> a + b
Если контекст позволяет, Java может определить тип параметров, что позволит вам написать то же лямбда-выражение, что и здесь:
(a, b) -> a + b
Варианты лямбда-выражений
Лямбда-выражения могут различаться по сложности в зависимости от требований. Они могут быть такими же простыми, как выражение без параметров, выполняющее простую задачу, или более сложными, принимающими несколько параметров и выполняющими блок кода. Вот несколько примеров: Без параметров: Лямбда-выражение без параметров можно использовать для реализации таких интерфейсов, как Runnable:
() -> System.out.println("Hello, world!")
Один параметр: Если имеется только один параметр и его тип определяется, круглые скобки можно опустить:
name -> System.out.println("Hello, " + name)
Несколько параметров: Для нескольких параметров обязательны круглые скобки:
(int a, int b) -> a * b
Блок кода: Если лямбда-выражение должно выполнять несколько операторов, используйте блок кода:
(String name) -> {
System.out.println("Name: " + name);
return name.length();
}
Возвращаемые значения и побочные эффекты
Лямбда-выражения могут возвращать значение. Тип возвращаемого значения определяется компилятором на основе контекста. Если тело лямбда-выражения содержит одно выражение, возвращается результат этого выражения. Для блока кода необходимо явно использовать оператор return. Также лямбда-выражения могут иметь побочные эффекты (например, изменение переменной-члена), если они обращаются к внешним переменным. Это известно как “захват переменных” (variable capture) и позволяет лямбда-выражениям взаимодействовать со своей средой, хотя и с определенными ограничениями для обеспечения безопасного и предсказуемого поведения.Вывод типов в лямбда-выражениях
Механизм вывода типов Java играет важную роль в читаемости лямбда-выражений. Компилятор определяет типы параметров лямбда-выражения на основе контекста, в котором используется лямбда-выражение. Эта функция уменьшает многословие кода и делает его чище. Например, при использовании лямбда-выражения с интерфейсом Comparator тип параметров можно опустить, поскольку компилятор может их определить:
Comparator<String> comparator = (s1, s2) -> s1.compareTo(s2);
Синтаксис и структура лямбда-выражений в Java обеспечивают баланс между простотой и гибкостью. Они позволяют разработчикам выражать сложные функциональные возможности с помощью минимального кода, улучшая читаемость и удобство обслуживания. По мере дальнейшего изучения мы увидим, что эти выражения являются не просто синтаксическим сахаром, а мощными инструментами, открывающими новые парадигмы программирования на Java.
Функциональные интерфейсы и лямбда-выражения
Функциональный интерфейс в Java — это интерфейс, содержащий ровно один абстрактный метод. Это ограничение имеет решающее значение, поскольку оно позволяет представлять экземпляры функциональных интерфейсов с помощью лямбда-выражений. Простота наличия единственного абстрактного метода означает, что тело лямбда-выражения неявно обеспечивает реализацию этого метода. В Java функциональные интерфейсы не являются новой концепцией. Они существовали еще в более ранних версиях Java, но в Java 8 они были формализованы с помощью аннотации @FunctionalInterface. Эта аннотация не является обязательной, но помогает четко указать назначение интерфейса и гарантирует, что интерфейс поддерживает контракт на наличие только одного абстрактного метода. Например:
@FunctionalInterface
public interface SimpleFunctionalInterface {
void execute();
}
Лямбда-выражения как реализации функциональных интерфейсов
Лямбда-выражения можно рассматривать как краткую реализацию функциональных интерфейсов. Когда лямбда-выражение присваивается переменной типа функционального интерфейса, оно обеспечивает реализацию единственного абстрактного метода интерфейса. Вот пример с учетом SimpleFunctionalInterface:
SimpleFunctionalInterface sfi = () -> System.out.println("Lambda implementation");
sfi.execute(); // Выводы: реализация Lambda
Этот код показывает, как лямбда-выражение используется для реализации метода execute SimpleFunctionalInterface.
Общие функциональные интерфейсы в Java
В Java 8 в пакете java.util.function появилось несколько встроенных функциональных интерфейсов, которые предназначены для покрытия большинства распространенных случаев использования лямбда-выражений. Некоторые из широко используемых функциональных интерфейсов включают в себя: Predicate<T>: Принимает аргумент типа T и возвращает логическое значение. Обычно используется для фильтрации данных.
Predicate<String> nonEmptyStringPredicate = s -> !s.isEmpty();
Function<T, R>: Принимает аргумент типа T и возвращает результат типа R. Часто используется для преобразования данных.
Function<String, Integer> stringLengthFunction = String::length;
Consumer<T>: Принимает аргумент типа T и не возвращает результат (void). Обычно используется для выполнения действий над объектами.
Consumer<String> printConsumer = System.out::println;
Supplier<T>: Представляет поставщика (supplier) результатов без входных аргументов.
Supplier<LocalDateTime> currentTimeSupplier = LocalDateTime::now;
В чем преимущество функциональных интерфейсов
Функциональные интерфейсы являются мощной концепцией в Java, поскольку они устраняют разрыв между функциональной и объектно-ориентированной парадигмами. Они позволяют писать более модульный, гибкий и легко тестируемый код. Например, методы могут быть разработаны так, чтобы принимать типы функциональных интерфейсов в качестве параметров, что делает их более универсальными и универсальными. Рассмотрим метод, который принимает Predicate<String>:
public static List<String> filterStrings(List<String> list, Predicate<String> predicate) {
return list.stream()
.filter(predicate)
.collect(Collectors.toList());
}
Этот метод можно использовать с различными лямбда-выражениями или ссылками на методы, каждый из которых обеспечивает различную логику фильтрации.
Функциональные интерфейсы и лямбда-выражения вместе привносят аспект функционального программирования в Java, язык, традиционно известный своими объектно-ориентированными функциями. Эта комбинация повышает выразительность Java и открывает мир возможностей для более краткого, читаемого и поддерживаемого кода. По мере того, как мы продолжаем изучать лямбда-выражения и их применение, становится очевидным, как эти концепции изменили то, как разработчики Java пишут код и думают о нем.
Лямбда-выражения в Stream API
Stream API, также представленный в Java 8, представляет собой значительный прогресс в обработке коллекций в Java. Он обеспечивает абстракцию высокого уровня для обработки последовательностей элементов, включая массивы, коллекции или ресурсы ввода-вывода. Stream API предназначен для поддержки операций функционального стиля, где лямбда-выражения играют ключевую роль.Совместное использование лямбда-выражений и потоков
Лямбда-выражения и Stream API вместе обеспечивают мощный и выразительный способ обработки коллекций в Java. До Java 8 перебор коллекций и применение таких операций, как фильтрация, сортировка или сопоставление, требовали многословного и часто повторяющегося кода. Stream API благодаря краткости лямбда-выражений упрощает эти операции. Например, рассмотрим задачу фильтрации списка строк на основе некоторых критериев и последующего преобразования каждой строки в ее форму в верхнем регистре. Как код выглядел до Java 8:
List<String> filteredList = new ArrayList<>();
for (String str : list) {
if (str.startsWith("J")) {
filteredList.add(str.toUpperCase());
}
}
С Java 8 Stream API и лямбда-выражениями:
List<String> filteredList = list.stream()
.filter(str -> str.startsWith("J"))
.map(String::toUpperCase)
.collect(Collectors.toList());
Ключевые операции с потоками с помощью лямбда-выражений
Stream API предоставляет богатый набор операций, которые можно разделить на промежуточные и терминальные операции. Лямбда-выражения обычно используются в следующих операциях: Filter: Применяет предикат к элементам потока, фильтруя несовпадающие элементы.
stream.filter(element -> element.matches("condition"))
Map: Преобразует каждый элемент потока с помощью предоставленной функции.
stream.map(element -> element.someTransformation())
Sorted: Сортирует элементы потока на основе компаратора, который можно кратко реализовать с помощью лямбда-выражения.
stream.sorted((e1, e2) -> e1.compareTo(e2))
ForEach: обходит каждый элемент потока. Лямбда-выражение определяет действие, выполняемое над каждым элементом.
stream.forEach(element -> System.out.println(element))
Reduce: Выполняет сокращение элементов потока с помощью заданной функции для получения единого совокупного результата.
stream.reduce(0, (subtotal, element) -> subtotal + element.someValue())
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ