The notation of this and other functional interfaces that return values, includes the listing of the return type as the last in the list of generics (R in this case) and the type of the input data in front of it (an input parameter of type T in this case). So, the notation Function<T, R> means that the only abstract method of this interface accepts an argument of type T and produces a result of type R. Let's look at the online documentation (https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/function/Function.html).
The Function<T, R> interface has one abstract method, R apply(T), and two methods for operations chaining:
- default <V> Function<T,V> andThen(Function<R, V> after): Returns a composed function that first applies the current function to its input, and then applies the after function to the result.
- default <V> Function<V,R> compose(Function<V, T> before): Returns a composed function that first applies the before function to its input, and then applies the current function to the result.
There is also an identity() method:
- static <T> Function<T,T> identity(): Returns a function that always returns its input argument
Let's review all these methods and how they can be used. Here is an example of the basic usage of the Function<T,R> interface:
Function<Integer, Double> multiplyByTen = i -> i * 10.0;
System.out.println(multiplyByTen.apply(1)); //prints: 10.0
We can also chain it with all the functions we have discussed in the preceding sections:
Supplier<Integer> supply7 = () -> 7;
Function<Integer, Double> multiplyByFive = i -> i * 5.0;
Consumer<String> printResult =
printWithPrefixAndPostfix("Result: ", " Great!");
printResult.accept(multiplyByFive.
apply(supply7.get()).toString()); //prints: Result: 35.0 Great!
The andThen() method allows for constructing a complex function from the simpler ones. Notice the divideByTwo.amdThen() line in the following code:
Function<Double, Long> divideByTwo =
d -> Double.valueOf(d / 2.).longValue();
Function<Long, String> incrementAndCreateString =
l -> String.valueOf(l + 1);
Function<Double, String> divideByTwoIncrementAndCreateString =
divideByTwo.andThen(incrementAndCreateString);
printResult.accept(divideByTwoIncrementAndCreateString.apply(4.));
//prints: Result: 3 Great!
It describes the sequence of the operations applied to the input value. Notice how the return type of the divideByTwo() function (Long) matches the input type of the incrementAndCreateString() function.
The compose() method accomplishes the same result, but in reverse order:
Function<Double, String> divideByTwoIncrementAndCreateString =
incrementAndCreateString.compose(divideByTwo);
printResult.accept(divideByTwoIncrementAndCreateString.apply(4.));
//prints: Result: 3 Great!
Now the sequence of composition of the complex function does not match the sequence of the execution. It may be very convenient in the case where the function divideByTwo() is not created yet and you would like to create it in-line. Then the following construct will not compile:
Function<Double, String> divideByTwoIncrementAndCreateString =
(d -> Double.valueOf(d / 2.).longValue())
.andThen(incrementAndCreateString);
The following line will compile just fine:
Function<Double, String> divideByTwoIncrementAndCreateString =
incrementAndCreateString
.compose(d -> Double.valueOf(d / 2.).longValue());
It allows for more flexibility while constructing a functional pipeline, so one can build it in a fluent style without breaking the continuous line when creating the next operations.
The identity() method is useful when you need to pass in a function that matches the required function signature but does nothing. But it can substitute only the function that returns the same type as the input type. For example:
Function<Double, Double> multiplyByTwo = d -> d * 2.0;
System.out.println(multiplyByTwo.apply(2.)); //prints: 4.0
multiplyByTwo = Function.identity();
System.out.println(multiplyByTwo.apply(2.)); //prints: 2.0
To demonstrate its usability, let's assume we have the following processing pipeline:
Function<Double, Double> multiplyByTwo = d -> d * 2.0;
System.out.println(multiplyByTwo.apply(2.)); //prints: 4.0
Function<Double, Long> subtract7 = d -> Math.round(d - 7);
System.out.println(subtract7.apply(11.0)); //prints: 4
long r = multiplyByTwo.andThen(subtract7).apply(2.);
System.out.println(r); //prints: -3
Then, we decide that, under certain circumstances, the multiplyByTwo() function should do nothing. We could add to it a conditional close that turns it on/off. But if we want to keep the function intact or if this function is passed to us from third-party code, we can just do the following:
Function<Double, Double> multiplyByTwo = d -> d * 2.0;
System.out.println(multiplyByTwo.apply(2.)); //prints: 4.0
Function<Double, Long> subtract7 = d -> Math.round(d - 7);
System.out.println(subtract7.apply(11.0)); //prints: 4
multiplyByTwo = Function.identity();
r = multiplyByTwo.andThen(subtract7).apply(2.);
System.out.println(r); //prints: -5
As you can see, now the multiplyByTwo() function does nothing, and the final result is different.