Логотип Workflow

Article

Functional Interfaces And Lambdas

Тема 11. Functional Interfaces and Lambdas

Функциональные интерфейсы и лямбды в Java нужны для передачи поведения как данных. Это не замена ООП, а дополнительный уровень выразительности: доменную модель вы по-прежнему описываете классами, а локальные правила и стратегии передаете как функции.

Lambda and Functional Interface

Базовая идея

До Java 8 часто приходилось писать анонимные классы даже для простого поведения. Лямбды сократили этот шаблонный код и сделали намерение более очевидным.

Функциональный интерфейс это интерфейс с одним абстрактным методом (SAM - Single Abstract Method).

@FunctionalInterface
interface MathOp {
    int apply(int a, int b);
}

MathOp add = (a, b) -> a + b;
System.out.println(add.apply(2, 3)); // 5

Где используется функциональный стиль в Java

  1. Коллекции (sort, removeIf, forEach).
  2. Stream API (map, filter, reduce).
  3. Асинхронность (CompletableFuture).
  4. События, коллбеки, стратегии.
  5. Пулы потоков (Runnable, Callable).

Стандартные functional interfaces

  • Predicate<T>: T -> boolean.
  • Function<T, R>: T -> R.
  • Consumer<T>: T -> void.
  • Supplier<T>: () -> T.
  • UnaryOperator<T>: T -> T.
  • BinaryOperator<T>: (T, T) -> T.
  • BiFunction<T, U, R>: (T, U) -> R.

Пример 1. Predicate + Function

Predicate<String> longName = s -> s.length() >= 4;
Function<String, String> upper = String::toUpperCase;

String result = List.of("ann", "alex").stream()
        .filter(longName)
        .map(upper)
        .findFirst()
        .orElse("NONE");

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

Пример 2. Consumer + Supplier

Supplier<UUID> idSupplier = UUID::randomUUID;
Consumer<UUID> printer = id -> System.out.println("ID=" + id);

UUID id = idSupplier.get();
printer.accept(id);

Синтаксис лямбд

Варианты:

() -> 42
x -> x * 2
(a, b) -> a + b
(String s) -> s.trim()
(a, b) -> {
    int r = a + b;
    return r;
}

Правило: чем короче и проще лямбда, тем лучше читаемость.

Method references

Method reference это сокращенный синтаксис, когда лямбда только вызывает существующий метод.

Виды:

  1. ClassName::staticMethod
  2. obj::instanceMethod
  3. ClassName::instanceMethod
  4. ClassName::new (ссылка на конструктор)

Пример:

List<String> names = new ArrayList<>(List.of("Bob", "Ann", "alex"));
names.sort(String::compareToIgnoreCase);
System.out.println(names); // [alex, Ann, Bob]

Замыкания и effectively final

Лямбды могут читать локальные переменные внешнего метода, если они final или effectively final.

int min = 3;
Predicate<String> p = s -> s.length() >= min;

Нельзя:

int min = 3;
min++; // после этого переменная уже не effectively final

Это ограничение защищает от неочевидных проблем с конкурентностью и временем жизни переменных.

Функциональные композиции

Вы можете собирать функции как конвейер:

Function<String, String> trim = String::trim;
Function<String, String> upper = String::toUpperCase;
Function<String, String> pipeline = trim.andThen(upper);

System.out.println(pipeline.apply("  java  ")); // JAVA

Для условий:

Predicate<String> notBlank = s -> !s.isBlank();
Predicate<String> shortWord = s -> s.length() <= 5;

Predicate<String> rule = notBlank.and(shortWord);
System.out.println(rule.test("java")); // true

Практические паттерны

Strategy через лямбду

interface DiscountStrategy {
    double apply(double amount);
}

double checkout(double amount, DiscountStrategy strategy) {
    return strategy.apply(amount);
}

double result = checkout(1000, a -> a * 0.9);

Callback для повторно используемой инфраструктуры

void withLogging(String opName, Runnable action) {
    long start = System.nanoTime();
    try {
        action.run();
    } finally {
        System.out.println(opName + " took " + (System.nanoTime() - start));
    }
}

Частые ошибки

  1. Слишком длинные лямбды с бизнес-логикой на пол-экрана.
  2. Побочные эффекты внутри stream-операций.
  3. Непонятные имена аргументов (x, y, z) в сложном коде.
  4. Избыточные собственные functional interfaces, когда есть готовые из java.util.function.

Когда лучше обычный метод

Лямбда не обязательна всегда. Вынесите логику в именованный метод, если:

  1. Нужна повторная переиспользуемость в нескольких местах.
  2. Лямбда становится длинной и плохо читаемой.
  3. Требуется отдельное тестирование сложной логики.

Что важно запомнить

  • Функциональные интерфейсы это контракт «поведение как значение».
  • Лямбды сокращают шаблонный код и улучшают выразительность.
  • Лучший стиль: короткие лямбды, чистые функции, минимум побочных эффектов.

Quiz

Проверьте, что вы усвоили

Авторизуйтесь чтоб пройти тесты