Освоение Java Lambda-выражений: от основ к Stream API
Освоение Java Lambda-выражений
Java 8 представила лямбда-выражения для повышения выразительности языка, позволяя разработчикам писать более лаконичный и читаемый код.
Прежде чем погружаться в лямбды, необходимо понять функциональные интерфейсы — фундамент реализации лямбд.
Что такое функциональный интерфейс?
Функциональный интерфейс — это Java-интерфейс, содержащий ровно один абстрактный метод. Этот единственный метод определяет назначение интерфейса.
Классический пример — интерфейс Runnable из java.lang, предоставляющий только метод run().
Пример 1: определение функционального интерфейса
import java.lang.FunctionalInterface;
@FunctionalInterface
public interface MyInterface {
// Единственный абстрактный метод
double getValue();
}
Аннотация @FunctionalInterface заставляет компилятор соблюдать контракт функционального интерфейса, предотвращая случайное добавление множественных абстрактных методов. Аннотация опциональна, но настоятельно рекомендуется для ясности и безопасности.
В Java 7 функциональные интерфейсы назывались Single Abstract Methods (SAM) и обычно реализовывались через анонимные классы.
Пример 2: реализация SAM через анонимный класс
public class FunctionInterfaceTest {
public static void main(String[] args) {
// Анонимный класс, реализующий Runnable
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("I just implemented the Runnable Functional Interface.");
}
}).start();
}
}
Вывод:
I just implemented the Runnable Functional Interface.
Хотя анонимные классы уменьшают шаблонный код по сравнению с традиционными реализациями, синтаксис остаётся громоздким. Лямбда-выражения устраняют необходимость явных имён методов при передаче функциональных интерфейсов как аргументов.
Введение в лямбда-выражения
Лямбда-выражение — это по сути анонимный метод, предоставляющий лаконичную реализацию абстрактного метода функционального интерфейса. Оно не выполняется независимо; вместо этого оно поставляет поведение для метода интерфейса.
Как определить лямбда-выражение в Java
Общий синтаксис:
(parameter list) -> lambda body
Оператор стрелки (->) разделяет список параметров и тело. Конкретные примеры ниже.
Предположим, метод возвращает Пи:
double getPiValue() { return 3.1415; }
С лямбдой:
() -> 3.1415
Без параметров левая часть стрелки пуста, а правая — тело выражения, возвращающее значение.
Типы тел лямбда-выражений
Тела лямбд в Java бывают двух форм:
1. Тело-выражение (Expression Body) — содержит одно выражение.
() -> System.out.println("Lambdas are great");
2. Блочное тело (Block Body) — заключено в фигурные скобки, позволяет множественные операторы и опциональный return.
() -> { double pi = 3.1415; return pi; };
Примечание: Тела-выражения не требуют явного return, тогда как блочные тела требуют его, если возвращается значение.
Пример 3: лямбда-выражение в практике
Напишем программу, возвращающую Пи через лямбда-выражение.
import java.lang.FunctionalInterface;
@FunctionalInterface
interface MyInterface {
double getPiValue();
}
public class Main {
public static void main(String[] args) {
MyInterface ref = () -> 3.1415;
System.out.println("Value of Pi = " + ref.getPiValue());
}
}
Вывод:
Value of Pi = 3.1415
Ключевые выводы:
MyInterfaceопределяет единственный абстрактный метод.- Ссылка на
MyInterfaceобъявлена, но не инстанцирована напрямую. - Лямбда-выражение поставляет реализацию для
getPiValue(). - Вызов
ref.getPiValue()запускает тело лямбды.
Лямбда-выражения с параметрами
Как методы, лямбды могут принимать параметры. Пример:
(n) -> (n % 2) == 0
Эта лямбда проверяет, является ли входное целое чётным.
Пример 4: реверс строки с параметризованной лямбдой
@FunctionalInterface
interface MyInterface {
String reverse(String s);
}
public class Main {
public static void main(String[] args) {
MyInterface ref = (str) -> {
String result = "";
for (int i = str.length() - 1; i >= 0; i--) {
result += str.charAt(i);
}
return result;
};
System.out.println("Lambda reversed = " + ref.reverse("Lambda"));
}
}
Вывод:
Lambda reversed = adbmaL
Обобщённый функциональный интерфейс
Функциональные интерфейсы могут быть обобщёнными, позволяя работать с любым типом данных. Например:
@FunctionalInterface
interface MyInterface<T> {
T func(T t);
}
Пример 5: обобщённые лямбды для строк и целых чисел
public class Main {
public static void main(String[] args) {
// Реверс строки
MyInterface<String> reverse = (str) -> {
String result = "";
for (int i = str.length() - 1; i >= 0; i--) {
result += str.charAt(i);
}
return result;
};
System.out.println("Lambda reversed = " + reverse.func("Lambda"));
// Факториал целого
MyInterface<Integer> factorial = (n) -> {
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
};
System.out.println("factorial of 5 = " + factorial.func(5));
}
}
Вывод:
Lambda reversed = adbmaL factorial of 5 = 120
Лямбда-выражения и Stream API
Пакет java.util.stream, введённый в JDK 8, даёт разработчикам возможность выполнять функциональные операции над коллекциями: фильтрацию, отображение, редукцию, сортировку и многое другое.
Рассмотрим список названий мест, каждое с префиксом страны. Используя стримы и лямбды, можно эффективно извлекать и манипулировать данными.
Пример 6: обработка потоков с лямбдами
import java.util.ArrayList;
import java.util.List;
public class StreamMain {
static List<String> places = new ArrayList<>();
public static List<String> getPlaces() {
places.add("Nepa, Kathmandu");
places.add("Nepa, Pokhara");
places.add("India, Delhi");
places.add("USA, New York");
places.add("Africa, Nigeria");
return places;
}
public static void main(String[] args) {
List<String> myPlaces = getPlaces();
System.out.println("Места из Непала:");
myPlaces.stream()
.filter(p -> p.startsWith("Nepa"))
.map(p -> p.toUpperCase())
.sorted()
.forEach(p -> System.out.println(p));
}
}
Вывод:
Места из Непала: NEPA, KATHMANDU NEPA, POKHARA
Здесь цепочка стрим-операций — filter, map, sorted, forEach — каждая принимает лямбду, что драматически сокращает шаблонный код по сравнению с pre-Java-8 решениями.
java
- Освоение интерфейсов в C#: определение, реализация и практические примеры
- Освоение операторов Java: типы, синтаксис и практические примеры
- Понимание выражений, операторов и блоков в Java
- Интерфейсы в Java: концепции, реализация и лучшие практики
- Try-with-resources в Java: автоматическое управление ресурсами
- Аннотации в Java: типы, размещение и практические примеры
- Интерфейс vs Абстрактный класс в Java: Как выбрать правильную абстракцию
- Регулярные выражения в Java: Мастерство сопоставления шаблонов с java.util.regex
- Интерфейсы в Java: определение, использование и лучшие практики
- Java 9 Private Interface Methods: инкапсуляция и повторное использование кода