В этой статье мы рассмотрим функциональный интерфейс Function (а точнее Function<T, R>), который появился, начиная с версии Java 1.8. Статья будет носить практический характер, нацеленный на демонстрацию некоторых примеров использования этого полезного функционального интерфейса.
Итак, функциональный интерфейс Function<T, R>, расположенный в стандартном пакете java.util.function, имеет один абстрактный метод, требующий обязательной реализации:
R apply(T t);
Также у него есть пара методов с названиями compose и andThen с реализацией по умолчанию (default):
Контракт метода compose:
default <V> Function<V, R> compose(Function<? super V, ? extends T> before)
Контракт метода andThen:
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after)
Мы рассмотрим два основных сценария использования функционального интерфейса Function<T, R> в программе на Java:
- посредством определения отдельного класса, реализующего интерфейс Function<T, R>
- посредством использования лямбда-выражения непосредственно в коде, где требуется использование функционального интерфейса Function<T, R>
Для первого сценария давайте создадим новый класс Int2DoubleMultiplyFunction в пакете ru.allineed.samples.function_interface, который реализует интерфейс следующим образом:
package ru.allineed.samples.function_interface;
import java.util.function.Function;
/**
* [RU] Класс, реализующий функциональный интерфейс {@code Function<T, R>} как {@code Function<Integer, Double>}<br/>
* [EN] Class implementing the function interface {@code Function<T, R>} as {@code Function<Integer, Double>}
*/
public class Int2DoubleMultiplyFunction implements Function<Integer, Double> {
@Override
public Double apply(Integer integer) {
if (integer == null) {
return null;
}
if (integer == 0) {
return 0.;
}
return 2. * integer;
}
}
Здесь мы реализуем функциональный интерфейс Function<T, R> с двумя аргументами типа - Integer и Double. Это означает, что наша функция будет принимать на вход параметр типа Integer, а возвращать будет значение типа Double - это хорошо прослеживается в контракте метода apply.
Логика метода apply в нашем примере простая:
- если на вход было подано значение null, то функция также вернёт null
- если входной параметр равен 0, то вернётся значение 0. с типом Double
- если значение входного параметра integer отрицательное или положительное (т.е. не равно нулю), то функция вернёт это значение, умноженное на 2
Теперь создадим класс FunctionInterfaceExample для проверки нашей реализации функционального интерфейса:
package ru.allineed.samples.function_interface;
import ru.allineed.samples.config.Localization;
import java.util.function.Function;
public class FunctionInterfaceExample {
public static void main(String[] args) {
Function<Integer, Double> funcLambda = (Integer intVal) -> {
if (intVal == null) {
return null;
}
if (intVal == 0) {
return 0.;
}
return 2. * intVal;
};
Int2DoubleMultiplyFunction funcInstance = new Int2DoubleMultiplyFunction();
String sampleTitle = Localization.getLocalized(
"Пример использования функции, заданной через лямбда-выражение:",
"Example of using a function specified as lambda expression:");
applyFunction(sampleTitle, funcLambda);
sampleTitle = Localization.getLocalized(
"Пример использования функции, заданной через отдельный экземпляр класса Int2DoubleMultiplyFunction:",
"Example of using a function specified as a separate instance of Int2DoubleMultiplyFunction class:");
applyFunction(sampleTitle, funcInstance);
Function<Double, Double> identityFunc = Function.identity();
Double identityResult = identityFunc.apply(357.218);
System.out.println();
System.out.format("identityResult = %.5f%n", identityResult);
}
/**
* [RU] Применяет переданную функцию {@code func} к различным входным аргументам<br/>
* [EN] Applies the given function {@code func} to the different input arguments
* @param title [RU] Текст заголовка для вывода на консоль; [EN] Header text for console output
* @param func [RU] Экземпляр функции {@code Function<Integer, Double>}; [EN] An instance of function
* {@code Function<Integer, Double>}
*/
public static void applyFunction(String title, Function<Integer, Double> func) {
System.out.println();
System.out.println(title);
Double zeroResult = func.apply(0);
System.out.format("zeroResult = %.2f%n", zeroResult);
Double negativeResult1 = func.apply(-200);
System.out.format("negativeResult1 = %.2f%n", negativeResult1);
Double negativeResult2 = func.apply(-50);
System.out.format("negativeResult2 = %.2f%n", negativeResult2);
Double positiveResult1 = func.apply(777);
System.out.format("positiveResult1 = %.2f%n", positiveResult1);
Double positiveResult2 = func.apply(55);
System.out.format("positiveResult2 = %.2f%n", positiveResult2);
Double nullResult = func.apply(null);
System.out.format("nullResult = %s%n", nullResult);
Double andThenResult = func.andThen(aDouble -> aDouble + 3).andThen(aDouble -> aDouble + 5).apply(10);
System.out.format("andThenResult = %.2f%n", andThenResult);
Double composeResult = func.compose(x -> (Integer)x + 5).compose(x -> (Integer)x + 1).apply(10);
System.out.format("composeResult = %.2f%n", composeResult);
}
}
В начале метода main мы создали экземпляр функции с именем funcLambda и задали через лямбда-выражение точно такое же тело метода, как у вышеупомянутого метода apply. Это сделано специально для того, чтобы показать сразу второй сценарий, где не требуется создавать отдельный класс, реализующий функциональный интерфейс Function<T, R>.
Далее мы создаём экземпляр нашего класса Int2DoubleMultiplyFunction и сохраняем ссылку на него в переменной funcInstance:
Int2DoubleMultiplyFunction funcInstance = new Int2DoubleMultiplyFunction();
В классе также есть метод applyFunction, который и демонстрирует возврат различных значений из функции для различных входных значений функции.
Сначала мы вызовем этот метод, передав ему заголовок сценария, где протестируем работу функции через лямбда-выражение, а также переменную funcLambda:
String sampleTitle = Localization.getLocalized(
"Пример использования функции, заданной через лямбда-выражение:",
"Example of using a function specified as lambda expression:");
applyFunction(sampleTitle, funcLambda);
При выполнении этого участка кода на экран консоли будет выведен следующий текст:
Пример использования функции, заданной через лямбда-выражение:
zeroResult = 0,00
negativeResult1 = -400,00
negativeResult2 = -100,00
positiveResult1 = 1554,00
positiveResult2 = 110,00
nullResult = null
andThenResult = 28,00
composeResult = 32,00
Далее мы делаем практически то же самое, но передаём экземпляр funcInstance для нашего отдельного класса Int2DoubleMultiplyFunction:
sampleTitle = Localization.getLocalized(
"Пример использования функции, заданной через отдельный экземпляр класса Int2DoubleMultiplyFunction:",
"Example of using a function specified as a separate instance of Int2DoubleMultiplyFunction class:");
applyFunction(sampleTitle, funcInstance);
Результат на экране консоли при запуске этого участка кода:
Пример использования функции, заданной через отдельный экземпляр класса Int2DoubleMultiplyFunction:
zeroResult = 0,00
negativeResult1 = -400,00
negativeResult2 = -100,00
positiveResult1 = 1554,00
positiveResult2 = 110,00
nullResult = null
andThenResult = 28,00
composeResult = 32,00
В самом конце метода main мы тестируем работу интересного статического метода identity, который есть у функционального интерфейса Function<T, R>:
Function<Double, Double> identityFunc = Function.identity();
Double identityResult = identityFunc.apply(357.218);
System.out.println();
System.out.format("identityResult = %.5f%n", identityResult);
Данный метод возвращает экземпляр для функции, которая будет возвращать то же самое значение, что подаётся ей на вход. В этом легко убедиться, запустив этот пример - на экран консоли будет выведено следующее значение переменной identityResult (обратите внимание, что оно равно тому же числу 357.218, что мы подали на вход методу apply):
identityResult = 357,21800
Давайте ещё разберём некоторые части метода applyFunction. Они во многом похожи и просто тестируют нашу функцию с разными входными значениями. Например, так мы вызываем нашу функцию с входным целочисленным значением 0, вызывая метод apply и записывая результат функции в переменную zeroResult:
Double zeroResult = func.apply(0);
System.out.format("zeroResult = %.2f%n", zeroResult);
Аналогичным же образом следуют участки кода с вызовом метода apply для разных целых чисел: -200, -50, 777, 55. Результаты вычисления нашей функции для этих чисел записываются в соответствующие переменные и также выводятся на экран консоли.
Отдельного внимания заслуживают методы andThen и compose. Метод andThen позволяет сцепить в цепочку дополнительные функции, которые будут применены и вычислены после того, как будет вызван метод apply и его реализация.
То есть для строки кода:
Double andThenResult = func.andThen(aDouble -> aDouble + 3).andThen(aDouble -> aDouble + 5).apply(10);
будет соблюдён следующий порядок вычисления:
- для входного значения 10 в метод apply будет сначала применена логика метода apply: т.к. число положительное, то функция вернёт результат 2 * 10 = 20
- далее будет вычислены функции, переданные в оба метода andThen, а именно: к предыдущему результату 20, который возвращает apply будет применено сложение с числами 5 и 3: 20 + 5 + 3 = 28
Метод compose работает противоположным образом - сначала он выполнит вычисления всех функций, переданных в compose, а в самом конце вызовет уже логику, находящуюся в реализации метода apply.
То есть для строки кода:
Double composeResult = func.compose(x -> (Integer)x + 5).compose(x -> (Integer)x + 1).apply(10);
будет соблюдён следующий порядок вычисления:
- для входного значения 10 в метод apply будут вычислены функции, переданные в метод compose: 10 + 1 + 5 = 16
- к получившемуся результирующему значению будет применена логика метода apply: т.е. на вход получим число 16, и так как оно положительное, то наша функция вернёт результат 2 * 16 = 32
Можете обратить внимание на вывод консоли с описанными результатами для переменных andThenResult и composeResult:
andThenResult = 28,00
composeResult = 32,00
На этом всё, в комментариях напишите свои мысли по поводу функционального интерфейса Function<T, R>, поделитесь насколько часто используете его при программировании на Java.
Внизу вы найдете ссылки на наш Git-репозиторий со всеми примерами на Java, а также ссылку на пример из этой статьи. Если вы ещё не настроили запуск примеров кода с нашего сайта, то внизу вы также найдете инструкцию по запуску примеров с сайта.
Примеры из этой статьи: https://github.com/AllineedRu/JavaExamples/blob/main/allineed-core/src/main/java/ru/allineed/samples/function_interface/FunctionInterfaceExample.java