Начиная с версии Java 1.8 появилась возможность определять функциональные интерфейсы. Функциональный интерфейс должен иметь не более одного абстрактного метода, это его основное ограничение. Также не запрещается для функционального интерфейса иметь методы по умолчанию (default methods), поскольку у них есть реализация.
Давайте посмотрим на то, как можно определить свой функциональный интерфейс на Java. Мы будем использовать среду разработки IntelliJ IDEA и создадим простой функциональный интерфейс для вычисления некой арифметической операции над двумя операндами first и second. Также давайте определим наш интерфейс с указанием параметра типа T (т.е. сделаем его Generic-интерфейсом, или "дженериком"), чтобы он мог вычислять арифметическую операцию над операндами произвольного типа.
Для простоты все классы мы зададим в пакете ru.allineed.samples.functional_interface, чтобы они "видели" друг друга и не пришлось прибегать к импортам.
Для задания функционального интерфейса нам требуется указать аннотацию @FunctionalInterface:
package ru.allineed.samples.functional_interface;
/**
* [RU] Простой функциональный интерфейс, выполняющий некоторую арифметическую операцию
* между двумя операндами<br/>
* [EN] A simple functional interface that performs some arithmetic operation between two
* operands
* @author <a href="https://allineed.ru">Allineed.Ru</a>
* @param <T> type of arguments for operation and corresponding type of operation result
*/
@FunctionalInterface
public interface ArithmeticOperation<T> {
/**
* [RU] Выполняет заданную операцию над двумя операндами<br/>
* [EN] Performs the specified operation between two operands
* @param first [RU] Первый операнд; [EN] First operand
* @param second [RU] Второй операнд; [EN] Second operand
* @return [RU] Возвращает результат операции того же типа, что и сами операнды; [EN] Returns
* the result of operation that has the same data type as the operands
*/
T doOperation(T first, T second);
}
Далее для примера давайте также определим два интерфейса-наследника, каждый из которых будет передавать уже конкретный аргумент типа.
Первый интерфейс-наследник будет выполнять операции над числами типа Double:
package ru.allineed.samples.functional_interface;
/**
* [RU] Интерфейс выполняет операции над числами с типом Double<br/>
* [EN] The interface performs operations between numbers of Double type
*/
public interface DoubleArithmeticOperation extends ArithmeticOperation<Double> {
}
Второй - аналогично, но уже над числами с типом Integer:
package ru.allineed.samples.functional_interface;
/**
* [RU] Интерфейс выполняет операции над числами с типом Integer<br/>
* [EN] The interface performs operations between numbers of Integer type
*/
public interface IntegerArithmeticOperation extends ArithmeticOperation<Integer> {
}
Теперь рядом со всеми этими интерфейсами создадим простой класс ArithmeticOperationExample с методом main() и несколькими отдельными статическими методами, которые демонстрируют примеры использования наших функциональных интерфейсов:
package ru.allineed.samples.functional_interface;
/**
* [RU] Основной исполняемый класс для статьи<br/>
* [EN] The main executable класс for the article<br/>
*/
public class ArithmeticOperationExample {
public static void main(String[] args) {
printDoubleOperationsDemo("Demo with Double operations");
printIntegerOperationsDemo("Demo with Integer operations");
printNumericOperationsDemo("Demo with mixed operations");
}
public static void printHeaderText(String headerText) {
System.out.println("=======================================");
System.out.println(headerText);
System.out.println("=======================================");
}
public static void printNumericOperationsDemo(String headerText) {
printHeaderText(headerText);
ArithmeticOperation<Double> divideDoubleOperation = (x, y) -> x / y;
ArithmeticOperation<Integer> divideIntegerOperation = (x, y) -> x / y;
Double resultDouble = divideDoubleOperation.doOperation(5d, 3d);
Integer resultInteger = divideIntegerOperation.doOperation(5, 3);
System.out.println("5 div 3 for Double Result: " + resultDouble);
System.out.println("5 div 3 for Integer Result: " + resultInteger);
}
public static void printIntegerOperationsDemo(String headerText) {
printHeaderText(headerText);
IntegerArithmeticOperation sumIntegerOperation = (x, y) -> x + y;
IntegerArithmeticOperation multiplyIntegerOperation = (x, y) -> x * y;
IntegerArithmeticOperation divideIntegerOperation = (x, y) -> x / y;
IntegerArithmeticOperation minusIntegerOperation = (x, y) -> x - y;
Integer sumIntegerResult = sumIntegerOperation.doOperation(5, 32);
Integer multiplyIntegerResult = multiplyIntegerOperation.doOperation(9, 7);
Integer divideIntegerResult = divideIntegerOperation.doOperation(34, 3);
Integer minusIntegerResult = minusIntegerOperation.doOperation(63, 25);
System.out.println("Sum Integer Result: " + sumIntegerResult);
System.out.println("Multiply Integer Result: " + multiplyIntegerResult);
System.out.println("Divide Integer Result: " + divideIntegerResult);
System.out.println("Minus Integer Result: " + minusIntegerResult);
}
public static void printDoubleOperationsDemo(String headerText) {
printHeaderText(headerText);
DoubleArithmeticOperation sumDoubleOperation = (x, y) -> x + y;
DoubleArithmeticOperation multiplyDoubleOperation = (x, y) -> x * y;
DoubleArithmeticOperation divideDoubleOperation = (x, y) -> x / y;
DoubleArithmeticOperation minusDoubleOperation = (x, y) -> x - y;
Double sumDoubleResult = sumDoubleOperation.doOperation(1.2, 2.5);
Double multiplyDoubleResult = multiplyDoubleOperation.doOperation(3.3, 7.4);
Double divideDoubleResult = divideDoubleOperation.doOperation(7.1, -6.2);
Double minusDoubleResult = minusDoubleOperation.doOperation(4.8, 3.8);
System.out.println("Sum Double Result: " + sumDoubleResult);
System.out.println("Multiply Double Result: " + multiplyDoubleResult);
System.out.println("Divide Double Result: " + divideDoubleResult);
System.out.println("Minus Double Result: " + minusDoubleResult);
}
}
Давайте быстро разберём, что здесь происходит. В методе main() мы последовательно вызываем три разных метода, каждый из них демонстрирует создание нескольких экземпляров созданных нами функциональных интерфейсов. Обратите внимание, что когда мы создаём экземпляры для интерфейсов DoubleArithmeticOperation, IntegerArithmeticOperation, то нам не требуется передавать аргумент типа, поскольку эти интерфейсы уже наследуют базовый интерфейс с указанием конкретного типа данных (Double и Integer, соответственно).
Метод printNumericOperationsDemo() уже показывает, как можно создавать экземпляры базового функционального интерфейса для конкретного типа данных.
Обратите внимание: несмотря на то, что лямбда-выражение одинаковое (x, y) -> x / y, при вызове метода doOperation() нашего функционального интерфейса, нам требуется для случая аргументов типа Double указывать конкретные постфиксы - 5d, 3d, чтобы компилятор не считал эти константы как числа типа int и смог сделать автоматическое обёртывание в тип Double:
ArithmeticOperation<Double> divideDoubleOperation = (x, y) -> x / y;
ArithmeticOperation<Integer> divideIntegerOperation = (x, y) -> x / y;
Double resultDouble = divideDoubleOperation.doOperation(5d, 3d);
Integer resultInteger = divideIntegerOperation.doOperation(5, 3);
При запуске программы мы увидим следующий результат в консоли:
=======================================
Demo with Double operations
=======================================
Sum Double Result: 3.7
Multiply Double Result: 24.419999999999998
Divide Double Result: -1.1451612903225805
Minus Double Result: 1.0
=======================================
Demo with Integer operations
=======================================
Sum Integer Result: 37
Multiply Integer Result: 63
Divide Integer Result: 11
Minus Integer Result: 38
=======================================
Demo with mixed operations
=======================================
5 div 3 for Double Result: 1.6666666666666667
5 div 3 for Integer Result: 1
Process finished with exit code 0
Снова обратите внимание на последние строки вывода в консоли, где 5 делится на 3: когда вызывается метод интерфейса для типа Double, то результат выводится в виде десятичной дроби. Во втором же случае результат деления приводится к целому числу в меньшую сторону и равен 1.
В качестве упражнения Вы можете задать свой класс YourClass, который Вам по душе и "научить" его выполнять какие-то арифметические операции, создав экземпляр функционального интерфейса ArithmeticOperation<YourClass>.
Надеюсь, статья была полезна и дала общее понимание использования функциональных интерфейсов в Java. До встречи в следующих уроках. Напишите свой отзыв и/или вопросы в комментариях, если статья понравилась.
Примеры из этой статьи: https://github.com/AllineedRu/JavaExamples/blob/main/allineed-core/src/main/java/ru/allineed/samples/functional_interface/ArithmeticOperationExample.java