Что такое 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» в реальном сервисе.