Функциональное программирование в Java: продвинутые техники и Stream API

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Функциональное программирование в Java: продвинутый уровень

Функциональное программирование (ФП) — это парадигма, в которой вычисления рассматриваются как вычисление математических функций, избегающих изменения состояния и изменяемых данных. Java, будучи объектно-ориентированным языком, с выходом Java 8 получила мощные инструменты для ФП: лямбда-выражения, Stream API и функциональные интерфейсы. На продвинутом уровне эти возможности позволяют писать более лаконичный, безопасный и выразительный код, особенно при работе с коллекциями, асинхронными операциями и обработкой ошибок.

В этой статье мы рассмотрим продвинутые техники функционального программирования в Java: композицию функций, монады (Optional и CompletableFuture), Stream API с параллельной обработкой и каррирование.

1. Композиция функций и функциональные интерфейсы

В Java функциональные интерфейсы — это интерфейсы с одним абстрактным методом (SAM). Наиболее часто используемые: Function<T, R>, Predicate<T>, Consumer<T> и Supplier<T>. Продвинутое использование включает композицию функций с помощью методов andThen() и compose().

Пример: цепочка преобразований

import java.util.function.Function;

public class FunctionComposition {
    public static void main(String[] args) {
        // Функция: строка -> её длина
        Function<String, Integer> lengthFunction = String::length;
        // Функция: число -> его квадрат
        Function<Integer, Integer> squareFunction = x -> x * x;

        // Композиция: строка -> длина -> квадрат длины
        Function<String, Integer> composedFunction = lengthFunction.andThen(squareFunction);

        String text = "Hello, Java!";
        int result = composedFunction.apply(text); // 12 * 12 = 144
        System.out.println("Результат: " + result); // 144
    }
}

Метод andThen() применяет первую функцию, затем вторую. compose() делает обратное: сначала применяет переданную функцию, затем первую. Это позволяет строить гибкие и переиспользуемые цепочки преобразований.

Практический пример: фильтрация и преобразование списка

import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class PredicateAndFunction {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Charlie", "David", "Eve");

        Predicate<String> startsWithA = s -> s.startsWith("A");
        Function<String, String> toUpperCase = String::toUpperCase;

        List<String> result = names.stream()
                .filter(startsWithA)
                .map(toUpperCase)
                .collect(Collectors.toList());

        System.out.println(result); // [ALICE]
    }
}

2. Монады в Java: Optional и CompletableFuture

Монада — это паттерн, который инкапсулирует значение и предоставляет операции для его преобразования без явного разворачивания. В Java монады реализованы через Optional (для обработки null) и CompletableFuture (для асинхронных операций).

Optional как монада

Методы map(), flatMap() и filter() позволяют цепочечно работать с возможным отсутствием значения.

import java.util.Optional;

public class OptionalMonad {
    public static void main(String[] args) {
        // Создаем Optional с непустым значением
        Optional<String> optional = Optional.of("Hello");

        // Цепочка преобразований
        String result = optional
                .filter(s -> s.length() > 3)   // фильтр
                .map(String::toUpperCase)        // преобразование
                .orElse("Default");              // значение по умолчанию

        System.out.println(result); // HELLO

        // Пример с пустым Optional
        Optional<String> empty = Optional.empty();
        String emptyResult = empty
                .filter(s -> s.length() > 3)
                .map(String::toUpperCase)
                .orElse("Default");

        System.out.println(emptyResult); // Default
    }
}

CompletableFuture для асинхронных цепочек

CompletableFuture позволяет объединять асинхронные задачи, обрабатывать ошибки и комбинировать результаты.

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureMonad {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // Асинхронное получение данных
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000); // имитация долгой операции
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            return "Data from remote";
        });

        // Цепочка преобразований
        CompletableFuture<Integer> processed = future
                .thenApply(String::length)   // преобразование
                .thenApply(len -> len * 2);  // ещё одно

        System.out.println(processed.get()); // 32 (16 символов * 2)
    }
}

3. Продвинутый Stream API: параллельные потоки и кастомные коллекторы

Stream API — основа ФП в Java. На продвинутом уровне важны параллельные потоки (parallelStream()) и создание собственных коллекторов.

Параллельные потоки

Параллельные потоки используют ForkJoinPool для ускорения обработки больших данных. Однако они требуют аккуратности из-за состояния гонки и порядка выполнения.

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class ParallelStreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = IntStream.rangeClosed(1, 100)
                .boxed()
                .collect(Collectors.toList());

        // Параллельная обработка
        long sum = numbers.parallelStream()
                .filter(n -> n % 2 == 0)  // чётные числа
                .mapToLong(Integer::longValue)
                .sum();

        System.out.println("Сумма чётных чисел: " + sum); // 2550
    }
}

Кастомный коллектор

С помощью Collector.of() можно создать свой коллектор для специфических задач.

import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import                

Блоги

Рекомендаций по книгам