какие есть способы инстанцировать функциональные интерфейсы java
Функциональные интерфейсы Java
Термин функциональный интерфейс был введен в Java 8. Это интерфейс, который содержит только один абстрактный (не реализованный) метод. Может содержать стандартные и статические, которые имеют реализацию, в дополнение к одному нереализованному.
Вышеуказанное содержит только один метод, и этот метод не имеет реализации.
Обычно интерфейс не содержит реализации методов, которые он объявляет, но он может содержать реализации в методах по умолчанию или в статических. Ниже приведен еще один пример с реализациями некоторых методов:
Вышеупомянутый интерфейс все еще считается функциональным, поскольку он содержит только один не реализованный метод.
Реализация с помощью лямбда-выражения
Лямбда-выражение реализует единственный метод из интерфейса. Чтобы узнать, какой метод реализует лямбда-выражение, интерфейс может содержать только один не реализованный метод. Другими словами, он должен быть функциональным.
Встроенные функциональные интерфейсы
Есть разработанные виды для часто встречающихся вариантов использования, поэтому вам не нужно создавать свои собственные функциональные интерфейсы для каждого небольшого варианта использования.
Function
Интерфейс Function interface(java.util.function.Function) является одним из самых центральных функциональных интерфейсов. Представляет функцию (метод), которая принимает один параметр и возвращает одно значение. Вот как выглядит определение:
Интерфейс Function на самом деле содержит несколько дополнительных методов в дополнение к методам, перечисленным выше, но, поскольку все они поставляются с реализацией по умолчанию, вам не нужно реализовывать их.
Единственный метод, который необходимо реализовать для реализации интерфейса Function, – это apply(). Вот пример реализации функции:
В этой реализации функции реализован метод apply(), поэтому он принимает параметр Long в качестве параметра и возвращает Long. Вот пример использования вышеупомянутого класса AddThree:
Вы также можете реализовать Function с помощью лямбда-выражения:
Как видите, реализация Function теперь встроена в объявление переменной adderLambda, а не в отдельный класс. Это немного короче, плюс мы можем видеть непосредственно в приведенном выше коде, что он делает.
Predicate
Интерфейс Java Predicate, java.util.function.Predicate, представляет простую функцию, которая принимает одно значение в качестве параметра и возвращает true или false:
Интерфейс Predicate содержит больше методов, чем метод test(), но остальные являются стандартными или статическими, которые вам не нужно реализовывать.
Вы можете реализовать Predicate, используя класс, например так:
Вы также можете реализовать Predicate, используя лямбда-выражение:
Эта лямбда-реализация Predicate фактически делает то же самое, что и реализация выше, использующая класс.
UnaryOperator
Интерфейс Java UnaryOperator представляет операцию, которая принимает один параметр и возвращает параметр того же типа:
Интерфейс UnaryOperator может использоваться для представления операции, которая принимает конкретный объект в качестве параметра, изменяет этот объект и возвращает его снова – возможно, как часть цепочки обработки функционального потока.
BinaryOperator
BinaryOperator – это функциональный интерфейс, представляющий операцию, которая принимает два параметра и возвращает одно значение. Оба параметра и тип возвращаемого значения должны быть одного типа. Полезен при реализации функций, которые суммируют, вычитают, делят, умножают и т. д. Два элемента одного типа и возвращают третий элемент того же типа.
Supplier
Интерфейс Supplier – это функциональный интерфейс, представляющий функцию, которая предоставляет значение некоторых видов. Также можно рассматривать как фабричный интерфейс:
Эта реализация Java Supplier возвращает новый экземпляр Integer со случайным значением от 0 до 1000.
Consumer
Consumer – это функциональный интерфейс, представляющий функцию, которая потребляет значение без возврата какого-либо значения. Реализация может распечатывать значение или записывать его в файл, или по сети и т. д. Реализация:
Функциональные интерфейсы и лямбда-выражения в Java
Что это такое, зачем нужно и как работает.
Вы наверняка знакомы с ситуацией, когда в разных частях программы должен выполняться один и тот же код, а различие лишь в данных, которые он будет обрабатывать. И тогда кусок кода не дублируют, а поступают хитрее — создают метод. И этот метод вызывают в нужных местах.
Передача простых параметров в виде примитивов или объектов труда обычно не составляет. Но порой в метод требуется передавать не просто переменную, а исполняемый код.
Например, нам нужен метод, который работает с элементами массива, причём только с теми, что соответствуют некоторому условию. А само условие во время написания метода мы можем не знать (или оно будет меняться).
Как поступить? Передавать реализующий условие код с помощью параметра метода! Да, в Java начиная с восьмой версии можно подобное делать. И сейчас вы узнаете как.
Хлебом не корми — дай кому-нибудь про Java рассказать.
Пример
Напишем методы, возвращающие сумму и произведение двух чисел:
А теперь объединим их в один — processTwoNumbers. Он будет принимать два параметра-числа и код, который их обрабатывает.
private int processTwoNumbers ( int a, int b, [сюда передаётся код])
Для выполнения метода sum третий параметр примет в качестве аргумента действие a+b, а для выполнения метода mult — a*b.
Обратите внимание, что третьим аргументом может быть передан не любой код, а только тот, который принимает на вход два параметра заданного типа (у нас int) и возвращает переменную нужного типа ( int).
Значит, надо как-то сообщить об этом компилятору — запретить будущим разработчикам передавать неподходящий код (вроде a+b+c).
Поможет в этом сигнатура метода. Она станет третьим параметром в нашем методе processTwoNumbers:
private int processTwoNumbers ( int a, int b, [сигнатура метода])
Но как записать третий параметр, чтобы сигнатура самого метода processTwoNumbers не разрослась до нечитабельности? Этот вопрос разработчики Java решили изящно. Они придумали функциональные интерфейсы.
Что такое функциональный интерфейс
Функциональный интерфейс — это интерфейс, который содержит ровно один абстрактный метод, то есть описание метода без тела. Статические методы и методы по умолчанию при этом не в счёт, их в функциональном интерфейсе может быть сколько угодно.
Когда параметром метода является функциональный интерфейс, при вызове этого метода одним из аргументов должен быть блок кода.
Передаваемый блок кода должен удовлетворять следующему условию: его сигнатура должна совпадать с сигнатурой единственного абстрактного метода функционального интерфейса.
Звучит непросто, поясним на примере:
Важно. В Java есть несколько готовых функциональных интерфейсов с разным числом и типами входных-выходных параметров. (Как раз из таких ToIntBiFunction выше.) А если мы создаём новый функциональный интерфейс, то важно не забыть аннотацию @FunctionalInterface. Увидев её, компилятор проверит, что интерфейс и правда является функциональным.
Функциональный интерфейс ToIntBiFunction подходит к тому примеру, с которого мы начинали. Это значит, что мы можем передать в него аргументом код, который:
Кусочек ToIntBiFunction говорит: передавай сюда метод с такой же сигнатурой, как у метода внутри меня.
Чтобы внутри метода processTwoNumbers выполнить переданный код, нужно вызвать метод из функционального интерфейса:
Вот мы и добрались до лямбда-выражений.
Что такое лямбда-выражения
Это компактный синтаксис, заимствованный из λ-исчисления, для передачи кода в качестве параметра в другой код.
По сути, это анонимный (без имени) класс или метод. Так как всё в Java (за исключением примитивных типов) — это объекты, лямбды тоже должны быть связаны с конкретным объектным типом. Как вы догадались, он называется функциональным интерфейсом.
То есть лямбда-выражение не выполняется само по себе, а нужно для реализации метода, который определён в функциональном интерфейсе.
Не будь лямбд, вызывать метод processTwoNumbers каждый раз приходилось бы так:
Примечание. biFunction в примере создана с использованием анонимных классов. Без этого нам пришлось бы создавать класс, реализующий интерфейс ToIntBiFunction, и объявлять в этом классе метод applyAsInt. А с анонимным классом мы всё это сделали на лету.
В примере выше всё, кроме одной строчки, избыточно. За содержательную часть (логику работы) отвечает только одно выражение return a + b, а всё остальное — техническая шелуха. И её пришлось написать многовато, даже чтобы просто передать методу код сложения двух чисел.
Здесь и вступают в игру лямбды. С ними можно сократить создание biFunction всего до десяти символов!
А наша лямбда будет такой:
И всё! Этот блок из 10 символов можно передавать как аргумент методу, ожидающему функциональный интерфейс в качестве параметра. Причём чаще всего обходятся без промежуточной переменной — передают напрямую лямбду:
Компилятор проверит, что лямбда подходит функциональному интерфейсу — принимает нужное число параметров нужного типа. Напомню, в нашем примере задействован функциональный интерфейс ToIntBiFunction. Сигнатура его единственного абстрактного метода содержит два параметра ( Integer a, Integer b).
Например, такой вот вызов метода не скомпилируется, потому что передан всего один параметр:
Лямбды записывают по-разному. Мы рассмотрели только один вариант.
Где применяют лямбды?
Много где. Довольно частый случай — обход элементов в цикле:
Ещё лямбды работают в компараторах при сортировке. Допустим, нужно отсортировать коллекцию по последней букве каждого слова:
Редко обходятся без лямбд при работе с коллекциями вместе со Stream API. В следующем примере фильтруем стрим по значению ( filter), меняем каждый элемент ( map) и собираем в список ( collect):
Подытожим
Функциональные интерфейсы в Java 8 избавили разработчиков от чудовищно громоздкого синтаксиса с анонимными классами (когда требовалось передавать некую функциональность в метод) и позволили использовать компактные лямбда-выражения и ссылки на методы.
Сперва синтаксическим сахаром были функциональные интерфейсы, они позволили оперировать блоком кода, который выполняется когда нужно, но реализации были слишком громоздкими. А с лямбдами функциональные интерфейсы стали записываться короче. Так что лямбды — не просто синтаксический сахар, а синтаксический сахар синтаксического сахара.
Теперь на Java можно писать программы в стиле функциональных языков программирования (это когда программа записывается как последовательное применение функций к некоторым значениям и другим функциям, а не как сложная структура из циклов, условных операторов и перекладывания значений туда-сюда). Удивительно, как легко превратить массивные структуры кода в изящные цепочки вызовов, и всё это благодаря лямбдам и функциональным интерфейсам.
Функциональные интерфейсы в 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. Попробуем объединить эти интерфейсы, в этом нам помогут обобщения:
Обобщения позволили нам сократить наш код на один функциональный интерфейс и на один метод. Теперь наш функциональный класс совместим с любыми лямбда-выражениями, принимающими класс Car и возвращающими объекты любого типа.
Предопределенные функциональные интерфейсы в Java 8.
Ранее мы всегда определяли собственные функциональные интерфейсы, но зачастую в этом нет необходимости, так как в JDK 8 представлены собственные функциональные интерфейсы, которые представлены в пакете java.util.function. Давайте рассмотрим некоторые из них:
Функциональные интерфейсы в Java 8
С остальными функциональными интерфейсами, представленными в Java 8, вы можете ознакомиться на сайте Oracle. Настало время рассмотреть каждый функциональный интерфейс более подробно.
Supplier.
Попробуем применить этот функциональный интерфейс на практике. Создадим строку и выведем ее содержимое на экран:
Обратите внимание, для того чтобы начать использовать встроенные функциональные интерфейсы мы должны их импортировать import java.util.function*. Первой строчкой метода main() мы объявляем интерфейс Supplier с обобщенным типом String и присваиваем его промежуточной переменной sup. Основное предназначение этого интерфейса – создание новых объектов, давайте попробуем создать список с типом String:
В нашем примере используется вложенное обобщение, первое обобщение для Supplier – ArrayList и второе обобщение, оно же вложенное для ArrayList – String. Единственное, что делает Supplier из последнего примера, так это создает пустой строковый список.
Consumer и BiConsumer.
Отличие Consumer от BiConsumer только в количестве параметров, для BiConsumer параметров у метода accept два. Это характерно для всех интерфейсов, если видите приставку Bi значит в функциональном интерфейсе используется два параметра.
В примере Consumer выводит символьную строку, которую в него передали, на экран. Давайте переделаем предыдущий пример для BiConsumer:
На этот раз при объявлении функционального интерфейса мы указываем два типа обобщения и в лямбду, соответственно, тоже передаем две переменных. Обобщения в BiConsumer могут быть разных типов:
На этот раз BiConsumer использует обобщения разных типов (String и Integer) и помогает нам заполнить HashMap двумя парами – ключ-значение.
Predicate и BiPredicate.
В метод test() передается один или два параметра, в зависимости от функционального интерфейса, а возвращается логическое значение true или false. Посмотрим, как это работает на практике, для начала проверим пустая строка или нет:
Выполнение этой программы выведет в консоль true. В лямбда выражение мы передаем строковое значение, к которому применяем метод isEmpty(), результат выполнения этого метода лямбда выражение возвращает обратно. Давайте теперь сравним две строки с помощью BiPredicate:
В этот раз мы передали в лямбда выражение две строки и вернули из него результат выполнения метода equals().
Function и BiFunction.
Попробуем посчитать количество символов в строке с помощью функционального интерфейса Function:
Обратите внимание, несмотря на то, что в лямбда выражение передается один параметр, в обобщениях мы все равно должны указать тип возвращаемого значения (в нашем случае Integer). Преобразуем строку из строчных букв, в строку из прописных букв:
Теперь воспользуемся возможностями функционального интерфейса BiFunction и объединим две строки:
Первых два обобщения в BiFunction определяют тип входных параметров, третье обобщение – возвращаемый тип.
UnaryOperator и BinaryOperator
Иначе говоря, использование этого интерфейса будет выглядеть как использования Function или BiFunction:
Воспользуемся функциональным интерфейсом UnaryOperator для реверса строки:
Настала очередь BinaryOperator, объединим две строки:
Обратите внимание, что, не смотря на три параметра (два входных и один для возвращаемого значения) в обобщении мы указываем только один тип – StringBuilder. Потому что, как говорилось ранее для функциональных интерфейсов UnaryOperator и BinaryOperator обобщенные типы должны совпадать и указывать три одинаковых типа обобщения просто не имеет смысла.
Хотелось бы отметить, что большинство выше описанных функциональных интерфейсов помимо абстрактных методов содержат в себе статичные и методы по умолчанию, которые были опущены при описании этих интерфейсов, но в ряде ситуаций они могут оказать колоссальную помощь, для того, чтобы ознакомиться с ними можете пройти по ссылке на официальное руководство Oracle по функциональным интерфейсам.
Функциональные интерфейсы в Java 8 для примитивных типов.
Большинство функциональных интерфейсов для работы с примитивами очень похожи на своих старших братьев, которые мы рассматривали ранее, рассмотрим их подробнее:
Функциональные интерфейсы в Java 8 для примитивных типов
Подробно эти интерфейсы мы рассматривать не будем, их принцип работы схож с теми, которые мы рассматривали ранее для обобщенных типов, остановимся только на некоторых особенностях. Функциональный интерфейс Function единственный, который возвращает обобщенный тип, все остальные либо ничего не возвращают, либо возвращают примитивные типы. BiConsumer, BiPredicate и BiFunction не используются для работы с примитивами, поэтому их в таблицы нет. В дополнение к описанным выше функциональным интерфейсам, в Java представлены функциональные интерфейсы характерные только для примитивов:
Функциональные интерфейсы в Java 8 для примитивов
Интерфейсы в Java и немного о полиморфизме
Интерфейс – это контракт, в рамках которого части программы, зачастую написанные разными людьми, взаимодействуют между собой и со внешними приложениями. Интерфейсы работают со слоями сервисов, безопасности, DAO и т.д. Это позволяет создавать модульные конструкции, в которых для изменения одного элемента не нужно трогать остальные.
Новички часто спрашивают, чем интерфейс отличается от абстрактного класса. Интерфейсы в Java компенсируют отсутствие множественного наследования классов. У класса-потомка может быть только один абстрактный класс-родитель, а вот интерфейсов класс может применять (имплементировать) сколько угодно.
Интерфейс на Java объявляют примерно так же, как и класс:
В имплементирующем интерфейс классе должны быть реализованы все предусмотренные интерфейсом методы, за исключением методов по умолчанию.
Методы по умолчанию впервые появились в Java 8. Их обозначают модификатором default. В нашем примере это метод say_goodbye, реализация которого прописана прямо в интерфейсе. Дефолтные методы изначально готовы к использованию, но при необходимости их можно переопределять в применяющих интерфейс классах.
Функциональный интерфейс Java
Если у интерфейса только один абстрактный метод, перед нами функциональный интерфейс. Его принято помечать аннотацией @FunctionalInterface, которая указывает компилятору, что при обнаружении второго абстрактного метода в этом интерфейсе нужно сообщить об ошибке. Стандартных (default) методов у интерфейса может быть множество – в том числе принадлежащих классу java.lang.Object.
Как выглядит функциональный интерфейс на Java:
Функциональные интерфейсы появились в Java 8. Они обеспечили поддержку лямбда-выражений, использование которых делает код лаконичным и понятным:
В той же версии появились пакеты встроенных интерфейсов: java.util.function и java.util.stream.
Реализация интерфейсов классами Java
Допустим, есть интерфейс Edible, которым пользуются классы Fruit, Vegetable, Fish. Экземпляры этих классов можно создавать так:
Обратите внимание на разницу в конструкторах: для фруктов задаём название и сорт, для рыбы – название, район вылова и вес порции в граммах. Но ссылки на оба объекта храним в переменных одного типа – «Съестное».
Интерфейсы и полиморфизм
Пример выше иллюстрирует один из трех основополагающих принципов ООП — полиморфизм. Мы раскрыли одно и то же явление — съедобность — через несколько классов, свойства и методы которых частично отличаются. Представление разных форм одного явления — это и есть полиморфизм. Если нужно, такую систему всегда можно расширить и скорректировать. В нашем случае — добавить новые виды съестного и методы их приготовления.
В Java полиморфизм можно реализовать через:
Интерфейс выручает в ситуации, когда при создании переменной мы не знаем, объект какого класса ей будет присвоен.
Интерфейс – это контракт, в рамках которого части программы, зачастую написанные разными людьми, взаимодействуют между собой и со внешними приложениями. Интерфейсы работают со слоями сервисов, безопасности, DAO и т.д. Это позволяет создавать модульные конструкции, в которых для изменения одного элемента не нужно трогать остальные.
Новички часто спрашивают, чем интерфейс отличается от абстрактного класса. Интерфейсы в Java компенсируют отсутствие множественного наследования классов. У класса-потомка может быть только один абстрактный класс-родитель, а вот интерфейсов класс может применять (имплементировать) сколько угодно.
Интерфейс на Java объявляют примерно так же, как и класс:
В имплементирующем интерфейс классе должны быть реализованы все предусмотренные интерфейсом методы, за исключением методов по умолчанию.
Методы по умолчанию впервые появились в Java 8. Их обозначают модификатором default. В нашем примере это метод say_goodbye, реализация которого прописана прямо в интерфейсе. Дефолтные методы изначально готовы к использованию, но при необходимости их можно переопределять в применяющих интерфейс классах.
Функциональный интерфейс Java
Если у интерфейса только один абстрактный метод, перед нами функциональный интерфейс. Его принято помечать аннотацией @FunctionalInterface, которая указывает компилятору, что при обнаружении второго абстрактного метода в этом интерфейсе нужно сообщить об ошибке. Стандартных (default) методов у интерфейса может быть множество – в том числе принадлежащих классу java.lang.Object.
Как выглядит функциональный интерфейс на Java:
Функциональные интерфейсы появились в Java 8. Они обеспечили поддержку лямбда-выражений, использование которых делает код лаконичным и понятным:
В той же версии появились пакеты встроенных интерфейсов: java.util.function и java.util.stream.
Реализация интерфейсов классами Java
Допустим, есть интерфейс Edible, которым пользуются классы Fruit, Vegetable, Fish. Экземпляры этих классов можно создавать так:
Обратите внимание на разницу в конструкторах: для фруктов задаём название и сорт, для рыбы – название, район вылова и вес порции в граммах. Но ссылки на оба объекта храним в переменных одного типа – «Съестное».
Интерфейсы и полиморфизм
Пример выше иллюстрирует один из трех основополагающих принципов ООП — полиморфизм. Мы раскрыли одно и то же явление — съедобность — через несколько классов, свойства и методы которых частично отличаются. Представление разных форм одного явления — это и есть полиморфизм. Если нужно, такую систему всегда можно расширить и скорректировать. В нашем случае — добавить новые виды съестного и методы их приготовления.
В Java полиморфизм можно реализовать через:
Интерфейс выручает в ситуации, когда при создании переменной мы не знаем, объект какого класса ей будет присвоен.