Различные типы функциональных интерфейсов, доступных в JDK

В предыдущей части мы узнали о функциональных возможностях Java. На этот раз мы рассмотрим функциональные интерфейсы, которые включены в JDK с момента появления в Java 8 лямбда-выражений.

Пакет java.util.function предоставляет 43 функциональных интерфейса, содержащих все строительные блоки, необходимые для создания довольно сложного функционального кода.

Большая четверка

Четыре функциональных интерфейса, которые мы, вероятно, будем использовать чаще всего:

  • T Supplier<T>#get() - не принимает аргументов, но возвращает объект. Ссылка на метод простого POJO-Getter квалифицируется как Supplier.
  • void Consumer<T>#accept(T t) - принимает единственный аргумент и ничего не возвращает. Каждый POJO-Setter квалифицируется как Consumer.
  • R Function<T, R>#apply(T t) - принимает единственный аргумент и возвращает объект.
  • boolean Predicate<T>#test(T t) - специализированная функция, которая принимает объект и возвращает примитив boolean.

С этими четырьмя только этими четырьмя мы можем выполнять много функционального программирования:

Функция Арность

Лямбды часто работают с тем же типом, что и аргумент (ы) и тип возвращаемого значения.

Поскольку Java является многословным языком, мы не хотим постоянно писать параметризованные типы. Чтобы избежать этого, Java предоставляет более специализированные интерфейсы с более простыми универсальными сигнатурами:

 Arity | Specialized       | Super-Interface
-------+-------------------+-------------------
   1   | UnaryOperator<T>  | Function<T,T>
   2   | BiConsumer<T,U>   | -
   2   | BiFunction<T,U,R> | -
   2   | BinaryOperator<T> | BiFunction<T,T,T>
   2   | BiPredicate<T,U>  | -

Имейте в виду, что вместо использования ...Operator<T> в качестве аргументов в методе мы должны предпочесть использовать его супер-интерфейс, чтобы сделать метод более гибким.

Примитивные типы

Пока не будут доступны Project Valhalla и универсальная специализация (JEP-218), мы не можем использовать примитивы в качестве параметризованных типов.

Несмотря на то, что автоматическая упаковка / распаковка работает «автоматически» во время компиляции, дополнительные накладные расходы на объем памяти и возможные последствия для производительности могут компенсировать первоначальную цель - сделать код более кратким и производительным.

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

Числовые примитивы

Числовые примитивы int, long и double имеют свои собственные специализированные функциональные интерфейсы. Вот те, что для int:

Все эти интерфейсы существуют для двух других типов long и double с соответствующими именами типов. Тот факт, что некоторые из примитивных типов отсутствуют, неплохо, их можно легко заменить существующими типами посредством приведения.

Функции преобразования

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

Логический

Примитив boolean не пользуется такой любовью, как примитивные числовые типы. Только один функциональный интерфейс имеет явный префикс:

Но Predicate<T> и его примитивные аналоги можно рассматривать как специализированные функциональные интерфейсы, возвращающие boolean примитивы.

Методы по умолчанию для функциональных интерфейсов

Для создания довольно сложных выражений или упрощения создания лямбда-выражений многие функциональные интерфейсы, предоставляемые JDK, имеют методы по умолчанию. Часто они проектируются как плавные интерфейсы.

Вот два примера, ознакомьтесь с функциональными интерфейсами, упомянутыми в этой статье, чтобы узнать больше.

Компаратор

Простой функциональный интерфейс для сравнения двух объектов одного типа. Давайте посмотрим на простой пример:

Comparator<MyBean> lambda =
    (lhs, rhs) -> lhs.getProperty().compareTo(rhs.getProperty());

Благодаря методу по умолчанию Comparator.comparing(Function<T,U>) мы можем упростить код:

Comparator<MyBean> lambda =
    Comparator.comparing(MyBean::getProperty);

Это проще, и его можно красиво встроить в Stream#sorted(Comparator<? super T> comparator).

Предикат

Другой функциональный интерфейс с методами по умолчанию - это уже упомянутый Predicate<T>. Он предоставляет все, основные строительные блоки для построения многокритериальной цепочки предикатов:

Заключение

JDK предоставляет нам множество различных функциональных интерфейсов. Поскольку в Java существует двойная система типов, различающая объектные типы и примитивы, существует потребность в специализированных интерфейсах для примитивов, если мы хотим обрабатывать их эффективно.

Свободные интерфейсы многих функциональных интерфейсов предоставляют множество частей для построения сложных выражений. Особенно в сочетании с Stream API и ссылками на методы мы можем создать довольно лаконичный и читаемый код.

Ресурсы