Функциональные интерфейсы

Тема дорожной карты · Java

Функциональные интерфейсы в Java — это интерфейсы ровно с одним абстрактным методом (SAM — Single Abstract Method), которые могут быть лаконично реализованы с помощью Lambda-выражений или ссылок на методы, образуя основу функционального стиля программирования, введённого в Java 8. Аннотация @FunctionalInterface необязательна, но рекомендуется — она включает проверку на этапе компиляции, что интерфейс имеет ровно один абстрактный метод, предотвращая случайное добавление второго, которое сломало бы все Lambda-использования. Пакет java.util.function содержит десятки готовых функциональных интерфейсов, организованных вокруг четырёх архетипов: Supplier<T> (создаёт значение без входных данных), Consumer<T> (потребляет значение, возвращает void), Function<T,R> (преобразует T в R) и Predicate<T> (проверяет T и возвращает boolean); специализированные варианты BiFunction<T,U,R>, UnaryOperator<T> и примитивно-специализированные IntSupplier, LongConsumer избегают накладных расходов на упаковку. Функциональные интерфейсы в Java питают весь Stream API — stream.filter(Predicate), stream.map(Function), stream.forEach(Consumer) и stream.collect(Collector) — все принимают функциональные интерфейсы, позволяя использовать лаконичные Lambda-выражения вместо многословных реализаций анонимных классов. При работе со Spring, Mockito, AssertJ или любой современной Java-библиотекой вы постоянно сталкиваетесь с Functional Interfaces: ApplicationListener<E>, Answer<T> Mockito и ThrowingCallable AssertJ — все они являются функциональными интерфейсами, принимающими Lambda-реализации.

Как это работает

Функциональные интерфейсы покрывает generics (параметры типа: class Box<T>, List<String>, Map<K, V>), wildcards (? extends, ? super, правило PECS), функциональные интерфейсы (один abstract-метод: Function, Consumer, Supplier, Predicate), лямбды (x -> x.toUpperCase()), method references (String::toUpperCase), Stream API. Type erasure означает, что generic-инфа исчезает на рантайме — работает на compile-time, но List<String>.class тот же, что List<Integer>.class.

Когда применять

Stream API — для трансформаций (.filter().map().collect()) когда intent яснее цикла. Generics — в API библиотек чтобы протолкнуть type-safety вызывающим. Применяйте PECS (Producer Extends, Consumer Super) при проектировании wildcards. Не over-engineer — Function<? super T, ? extends R> иногда правильный ответ, иногда запах.

Типичные ошибки

Ловушки Функциональные интерфейсы: List rawList = new ArrayList() (raw-типы — обходят generics, ломают type-safety); злоупотребление Stream .peek() для side-эффектов (это для дебага — forEach если нужны side-эффекты); stateful-лямбды (parallel streams + mutable shared state = race); instanceof + cast вместо pattern matching (Java 21+).

Связанные понятия

Полезные ресурсы