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

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

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

Начать курс

Функциональное программирование в 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);                

Блоги

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