Функциональные интерфейсы стандартной библиотеки
Базовая шестёрка
Пакет java.util.function содержит общие формы поведения:
| Интерфейс | SAM | Смысл |
|---|---|---|
Predicate<T> | boolean test(T) | проверить условие |
Function<T,R> | R apply(T) | преобразовать значение |
Consumer<T> | void accept(T) | выполнить побочный эффект |
Supplier<T> | T get() | лениво предоставить значение |
UnaryOperator<T> | T apply(T) | преобразование T → T |
BinaryOperator<T> | T apply(T,T) | объединение двух T в один T |
Predicate<String> valid = text -> text != null && !text.isBlank();
Function<String, Integer> length = String::length;
Consumer<String> log = message -> logger.info(message);
Supplier<Instant> now = clock::instant;
UnaryOperator<String> normalize = String::trim;
BinaryOperator<BigDecimal> add = BigDecimal::add;Выбор точного интерфейса выражает назначение лучше, чем универсальный собственный SAM.
Predicate
Предикаты составляются через короткозамкнутые and, or, negate:
Predicate<Order> paid = Order::paid;
Predicate<Order> recent =
order -> order.createdAt().isAfter(cutoff);
List<Order> selected = orders.stream()
.filter(paid.and(recent))
.toList();Predicate.isEqual(target) строит null-safe-проверку через Objects.equals.
Function
Порядок композиции важен:
Function<String, String> trim = String::trim;
Function<String, Integer> parse = Integer::parseInt;
Function<String, Integer> trimThenParse = trim.andThen(parse);
Function<String, Integer> same = parse.compose(trim);f.andThen(g)означает сначалаf, затемg;f.compose(g)означает сначалаg, затемf;Function.identity()возвращает аргумент без изменения.
Consumer и Supplier
Consumer обычно обозначает эффект: запись, логирование, накопление. andThen последовательно вызывает два consumer; если первый выбросил исключение, второй не выполнится.
Supplier выражает отложенное получение:
User user = cache.get(id)
.orElseGet(() -> repository.load(id));Поставщик вызывается только при необходимости, в отличие от заранее вычисленного аргумента orElse.
Bi-формы
Для двух аргументов есть BiPredicate<T,U>, BiFunction<T,U,R>, BiConsumer<T,U>.
BiFunction<BigDecimal, Integer, BigDecimal> multiply =
(price, quantity) -> price.multiply(BigDecimal.valueOf(quantity));BinaryOperator<T> — частный случай BiFunction<T,T,T>.
Примитивные специализации
Чтобы не создавать обёртки, библиотека содержит IntPredicate, LongSupplier, DoubleConsumer, IntFunction<R>, ToIntFunction<T> и другие.
ToIntFunction<String> length = String::length;
int total = names.stream().mapToInt(length).sum();Специализации особенно важны в больших потоках чисел.
Ловушки
- Использовать
Consumerвнутриmap;mapожидает результат преобразования. - Путать порядок
composeиandThen. - Вычислять дорогой fallback через
orElse, когда нуженorElseGet. - Выбирать boxed-интерфейс на горячем числовом пути.
- Делать
Predicateс неожиданным изменением состояния. - Использовать
BinaryOperatorдля неассоциативной операции в параллельномreduce.
Вопросы на собеседовании
- Чем
Predicate,FunctionиConsumerотличаются по контракту? - Когда использовать
UnaryOperatorвместоFunction? - В каком порядке выполняются
composeиandThen? - Зачем нужны primitive specializations?
- Чем
BiFunction<T,T,T>отличается по смыслу отBinaryOperator<T>? - Когда
Supplierпомогает избежать лишнего вычисления?
Связанные заметки
Функциональные интерфейсы · Лямбда выражения · Функциональное программирование · Stream API · Класс Optional