JavaJuniorTechnical

Что такое functional interface? Назовите несколько встроенных functional interfaces в Java.

Functional interface содержит ровно один абстрактный метод (SAM). Встроенные: Predicate (boolean test), Function (R apply), Consumer (void accept), Supplier (T get). Аннотация @FunctionalInterface добавляет compile-time проверку контракта.

Что такое functional interface

Functional interface — интерфейс, содержащий ровно один абстрактный метод (SAM — Single Abstract Method). Он может содержать любое количество default и static методов — они не нарушают контракт SAM. Functional interface можно присваивать лямбда-выражениям и method references.

@FunctionalInterface
public interface Transformer<T, R> {
    R transform(T input);

    // default метод — не нарушает SAM
    default Transformer<T, R> withLogging() {
        return input -> {
            R result = transform(input);
            System.out.println(input + " -> " + result);
            return result;
        };
    }
}

Transformer<String, Integer> lengthOf = String::length;  // method reference
Transformer<String, Integer> withLog = lengthOf.withLogging();
withLog.transform("hello");  // выведет: hello -> 5

Аннотация @FunctionalInterface

Аннотация опциональная, но настоятельно рекомендуется: компилятор проверяет SAM-контракт и выдаёт ошибку при добавлении второго абстрактного метода. Без неё интерфейс всё равно может использоваться как functional, но нет compile-time защиты от случайного нарушения контракта.

Встроенные functional interfaces (java.util.function)

  • Predicate<T>boolean test(T t). Фильтрация, проверки условий. Методы: and(), or(), negate().
  • Function<T, R>R apply(T t). Преобразование одного типа в другой. Методы: andThen(), compose().
  • Consumer<T>void accept(T t). Действие без возвращаемого значения (логирование, запись). Метод: andThen().
  • Supplier<T>T get(). Фабрика без аргументов (lazy инициализация).
  • BiFunction<T, U, R>R apply(T t, U u). Функция двух аргументов.
  • UnaryOperator<T> — специализация Function<T, T>. String::toUpperCase — пример.
  • BinaryOperator<T> — специализация BiFunction<T, T, T>. Используется в Stream.reduce().
  • Примитивные специализации: IntPredicate, LongFunction<R>, ToIntFunction<T> — избегают autoboxing.
import java.util.function.*;
import java.util.List;
import java.util.stream.Collectors;

record User(String name, String email, boolean active) {}

List<User> users = List.of(
    new User("Alice", "alice@example.com", true),
    new User("Bob",   "bob@example.com",   false),
    new User("Carol", "carol@example.com", true)
);

Predicate<User>      isActive   = User::active;
Function<User, String> getEmail = User::email;
Consumer<String>     logger     = email -> System.out.println("Sending to: " + email);

// Цепочка через Stream API
users.stream()
     .filter(isActive)          // Predicate
     .map(getEmail)             // Function
     .peek(logger)              // Consumer
     .collect(Collectors.toList());

// Supplier для lazy инициализации
Supplier<List<User>> fetchUsers = () -> List.of(new User("Dave", "d@x.com", true));
List<User> loaded = fetchUsers.get();

Подводные камни

  • Лямбда захватывает переменные из внешнего scope: они должны быть effectively final — компилятор откажет при изменении захваченной переменной.
  • Checked exceptions в лямбдах не поддерживаются без обёртки — Function<T, R> не объявляет throws, поэтому нужно либо оборачивать, либо создавать собственный интерфейс с checked exception.
  • Примитивные специализации (IntFunction, ToIntFunction) важны в высоконагруженных путях — использование Function<Integer, R> создаёт autoboxing на каждом вызове.
  • Comparator<T> — тоже functional interface, несмотря на то что содержит много default методов; его часто упускают в ответе.
  • Method reference на instance метод (str::contains) привязывается к конкретному объекту в момент создания — это отличается от лямбды s -> str.contains(s) только синтаксически, но может вызвать путаницу с this-захватом.
  • Два разных functional interface с одинаковой сигнатурой не взаимозаменяемы без явного приведения: Runnable и Callable<Void> — разные типы.
  • Перегруженные методы с несколькими functional interface параметрами могут вызвать ambiguity error компилятора — нужно явно указывать тип лямбды.

Common mistakes

  • Путать термин «functional interface» с соседним механизмом Java.
  • Не называть границу lifecycle, transaction, thread или request для «functional interface».
  • Игнорировать production-эффекты «functional interface»: latency, SQL shape, memory, security или observability.

What the interviewer is testing

  • Попросить объяснить механизм «functional interface» на минимальном примере.
  • Проверить, видит ли кандидат failure mode и диагностику для «functional interface».
  • Уточнить, какие настройки или API меняют «functional interface» в реальном сервисе.

Sources

Related topics