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

Функциональные интерфейсы в Java 8 – это интерфейсы, которые содержат в себе только один абстрактный метод. Функциональные интерфейсы имеют тесную связь с лямбда выражениями и служат как основа для применения лямбда выражений в функциональном программировании на Java. Хотелось бы напомнить один нюанс — до появления Java 8 все методы в интерфейсе неявно считались абстрактными. С выходом JDK 8 появилось такое понятие как метод по умолчанию. Метод по умолчанию – это метод объявленный в интерфейсы, поведение которого предопределено, иначе говоря, метод уже имеет реализацию в интерфейсе. Давайте рассмотрим пример функционального интерфейса:

functionalInterface, в нашем примере является типичным функциональным интерфейсом, который содержит в себе один абстрактный метод – abstractMethod(). Аннотация @FunctionalInterface не обязательна, но я бы рекомендовал ее использовать, хотя бы для самоконтроля:

При наличии аннотации компилятор нам сообщит, что наш интерфейс больше не является функциональным, так как мы добавили в него второй абстрактный метод – abstractMethod1(). Немного практики никогда не повредит:

как вы думаете какой из этих трех интерфейсов можно назвать функциональным?

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

Реализация функционального интерфейса, ничем не отличается от реализации обычного интерфейса:

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

Очередь за функциональным интерфейсом:

Последний штрих:

Вывод:
AudiA3
AudiA3
AudiA6

Метод printTest(), соответствует ли переданная характеристика машины истине(в нашем случае это наличие полного привода и бензиновый двигатель), если да – то метод выводит ее название. Как вы могли заметить, в метод для тестирования мы передавали лямбда выражения:

Первое выражение означает, вызов метода с параметром Car, который возвращает логическое выражение, которое в свою очередь является результатом вызова c.isFullDrive(). При вызове лямбда выражения Java полагается на его контекст. Если вы посмотрите на объявление метода printTest(), то в качестве второго параметра увидите функциональный интерфейс. Функциональный интерфейс содержит один метод test(), который принимает объект класса Car и возвращает логическое значение, на него лямбда выражение и проецируется. В самом же классе Car только два метода возвращают логическое значение, их мы и вызываем с помощью лямбд.



Обобщенные функциональные интерфейсы.

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

В класс Car была добавлена переменная для хранения информации об оборотах двигателя, а так же два метода гэттера для получения информации об оборотах и названии машины – getRPM() и getName(). Так же было добавлено два функциональных интерфейса GetName и GetRPM (не лучшие названия для интерфейсов, я знаю, но скоро мы от них избавимся). В главный класс программы были добавлены два метода для вывода на экран информации об оборотах и названия машины – printName() и printRPM(). Каждый из этих методов в качестве второго параметра принимает свой интерфейс. Вернемся к функциональным интерфейсам, если вы обратите внимание на методы в них, то заметите схожесть, отличие только в возвращаемом значении – String и Integer. Попробуем объединить эти интерфейсы, в этом нам помогут обобщения:

Вывод:
AudiA6
8000

Обобщения позволили нам сократить наш код на один функциональный интерфейс и на один метод. Теперь наш функциональный класс совместим с любыми лямбда-выражениями, принимающими класс Car и возвращающими объекты любого типа.

Предопределенные функциональные интерфейсы в Java 8.

Ранее мы всегда определяли собственные функциональные интерфейсы, но зачастую в этом нет необходимости, так как в JDK 8 представлены собственные функциональные интерфейсы, которые представлены в пакете java.util.function. Давайте рассмотрим некоторые из них:

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

С остальными функциональными интерфейсами, представленными в Java 8, вы можете ознакомиться на сайте Oracle. Настало время рассмотреть каждый функциональный интерфейс более подробно.

Supplier.


Supplier (поставщик) используется для создание какого-либо объекта без использования входных параметров. Интерфейс представлен в стандартной библиотеке Java в следующем виде:

Попробуем применить этот функциональный интерфейс на практике. Создадим строку и выведем ее содержимое на экран:

Обратите внимание, для того чтобы начать использовать встроенные функциональные интерфейсы мы должны их импортировать import java.util.function*. Первой строчкой метода main() мы объявляем интерфейс Supplier с обобщенным типом String и присваиваем его промежуточной переменной sup. Основное предназначение этого интерфейса – создание новых объектов, давайте попробуем создать список с типом String:

В нашем примере используется вложенное обобщение, первое обобщение для SupplierArrayList и второе обобщение, оно же вложенное для ArrayListString. Единственное, что делает Supplier из последнего примера, так это создает пустой строковый список.

Consumer и BiConsumer.


Consumer(потребитель) используется в том случае, если нам нужно применить какое-то действие или операцию к параметру (или к двум параметрам для BiConsumer) и при этом в возвращаемом значении нет необходимости.

Отличие Consumer от BiConsumer только в количестве параметров, для BiConsumer параметров у метода accept два. Это характерно для всех интерфейсов, если видите приставку Bi значит в функциональном интерфейсе используется два параметра.

В примере Consumer выводит символьную строку, которую в него передали, на экран. Давайте переделаем предыдущий пример для BiConsumer:

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

На этот раз BiConsumer использует обобщения разных типов (String и Integer) и помогает нам заполнить HashMap двумя парами – ключ-значение.



Predicate и BiPredicate.


Predicate(утверждение) наиболее часто применяется в фильтрах и сравнении, имеет следующий вид:

В метод test() передается один или два параметра, в зависимости от функционального интерфейса, а возвращается логическое значение true или false. Посмотрим, как это работает на практике, для начала проверим пустая строка или нет:

Выполнение этой программы выведет в консоль true. В лямбда выражение мы передаем строковое значение, к которому применяем метод isEmpty(), результат выполнения этого метода лямбда выражение возвращает обратно. Давайте теперь сравним две строки с помощью BiPredicate:

В этот раз мы передали в лямбда выражение две строки и вернули из него результат выполнения метода equals().

Function и BiFunction.


Function(функция) используется для преобразования входного параметра или в двух параметров (для BiFunction) в какое-либо значение, тип значение может не совпадать с типом входных параметров.

Попробуем посчитать количество символов в строке с помощью функционального интерфейса Function:

Обратите внимание, несмотря на то, что в лямбда выражение передается один параметр, в обобщениях мы все равно должны указать тип возвращаемого значения (в нашем случае Integer). Преобразуем строку из строчных букв, в строку из прописных букв:

Теперь воспользуемся возможностями функционального интерфейса BiFunction и объединим две строки:

Первых два обобщения в BiFunction определяют тип входных параметров, третье обобщение – возвращаемый тип.

UnaryOperator и BinaryOperator


UnaryOperator и BinaryOperator разновидность Function, в которых входные и выходные обобщенные параметры должны совпадать. Если заглянуть в пакет java.util.function, то можно заметить, что UnaryOperator расширяет Function, а BinaryOperator расширяет BiFunction.

Иначе говоря, использование этого интерфейса будет выглядеть как использования Function или BiFunction:

Воспользуемся функциональным интерфейсом UnaryOperator для реверса строки:

Настала очередь BinaryOperator, объединим две строки:

Обратите внимание, что, не смотря на три параметра (два входных и один для возвращаемого значения) в обобщении мы указываем только один тип – StringBuilder. Потому что, как говорилось ранее для функциональных интерфейсов UnaryOperator и BinaryOperator обобщенные типы должны совпадать и указывать три одинаковых типа обобщения просто не имеет смысла.
Хотелось бы отметить, что большинство выше описанных функциональных интерфейсов помимо абстрактных методов содержат в себе статичные и методы по умолчанию, которые были опущены при описании этих интерфейсов, но в ряде ситуаций они могут оказать колоссальную помощь, для того, чтобы ознакомиться с ними можете пройти по ссылке на официальное руководство Oracle по функциональным интерфейсам.



Функциональные интерфейсы в Java 8 для примитивных типов.

Большинство функциональных интерфейсов для работы с примитивами очень похожи на своих старших братьев, которые мы рассматривали ранее, рассмотрим их подробнее:

Функциональные интерфейсы в Java 8 для примитивных типов
Функциональные интерфейсы в Java 8 для примитивных типов

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

Функциональные интерфейсы в Java 8 для примитивов
Функциональные интерфейсы в Java 8 для примитивов

Site Footer