Функциональное программирование в Java: от А до Я
Функциональное программирование (ФП) — это парадигма, в которой вычисления рассматриваются как вычисление математических функций, избегая изменения состояния и изменяемых данных. Java, традиционно императивный язык, начиная с версии 8, активно внедряет элементы ФП. Это позволяет писать более лаконичный, предсказуемый и легко тестируемый код, особенно при работе с коллекциями и асинхронными операциями.
В этой статье мы разберем ключевые концепции ФП в Java: от лямбда-выражений до продвинутых техник работы со Stream API и Option. Вы узнаете, как применять эти инструменты на практике, чтобы ваш код стал чище и эффективнее.
1. Основы: Лямбда-выражения и Функциональные интерфейсы
Лямбда-выражение — это краткая форма записи анонимного метода (функции). Оно позволяет передавать поведение как данные. Синтаксис: (параметры) -> тело.
Функциональный интерфейс — это интерфейс с единственным абстрактным методом (SAM — Single Abstract Method). Именно такие интерфейсы могут быть реализованы с помощью лямбд. Ключевые встроенные интерфейсы в java.util.function:
Predicate<T>— принимает объект T, возвращает boolean (методtest).Function<T, R>— принимает T, возвращает R (методapply).Consumer<T>— принимает T, ничего не возвращает (методaccept).Supplier<T>— ничего не принимает, возвращает T (методget).UnaryOperator<T>— частный случай Function, где T и R одного типа.
Пример: Сортировка списка с Comparator (функциональный интерфейс)
import java.util.*;
public class LambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Иван", "Анна", "Петр", "Мария");
// Без лямбды (анонимный класс)
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
// С лямбдой
Collections.sort(names, (a, b) -> a.length() - b.length());
// Еще короче: используем Comparator.comparingInt
names.sort(Comparator.comparingInt(String::length));
System.out.println(names); // [Анна, Петр, Иван, Мария]
}
}
Пример: Использование Predicate для фильтрации
import java.util.function.Predicate;
public class PredicateExample {
public static void main(String[] args) {
Predicate<Integer> isEven = n -> n % 2 == 0;
Predicate<Integer> isPositive = n -> n > 0;
System.out.println(isEven.test(4)); // true
System.out.println(isEven.and(isPositive).test(-2)); // false (не положительное)
System.out.println(isEven.or(isPositive).test(-3)); // true (не четное, но положительное? нет, -3 не положительное)
}
}
2. Stream API: Мощный инструмент обработки данных
Stream API — это последовательность элементов, поддерживающая параллельные и последовательные операции. Stream не хранит данные, а преобразует их. Ключевые операции делятся на промежуточные (возвращают новый Stream) и терминальные (завершают обработку и возвращают результат или побочный эффект).
Основные операции Stream API:
- Промежуточные:
filter,map,flatMap,sorted,distinct,peek,limit,skip. - Терминальные:
collect,forEach,reduce,count,anyMatch,allMatch,noneMatch,findFirst,findAny.
Пример: Обработка списка чисел
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Получить квадраты четных чисел, отсортированные по убыванию
List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0) // Промежуточная: оставляем только четные
.map(n -> n * n) // Промежуточная: возводим в квадрат
.sorted((a, b) -> b - a) // Промежуточная: сортируем по убыванию
.collect(Collectors.toList()); // Терминальная: собираем в список
System.out.println(result); // [100, 64, 36, 16, 4]
}
}
Пример: Работа со строками и flatMap
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class FlatMapExample {
public static void main(String[] args) {
List<String> sentences = Arrays.asList(
"Hello world",
"Java functional programming",
"Stream API"
);
// Разбить предложения на слова и получить уникальные слова в нижнем регистре
List<String> uniqueWords = sentences.stream()
.flatMap(sentence -> Arrays.stream(sentence.split(" ")))
.map(String::toLowerCase)
.distinct()
.collect(Collectors.toList());
System.out.println(uniqueWords); // [hello, world, java, functional, programming, stream, api]
}
}
Пример: Использование reduce для агрегации
import java.util.Arrays;
import java.util.List;
public class ReduceExample {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// Сумма всех чисел (с начальным значением 0)
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);