Java8 has added a new feature, Function, which as the name implies must be a functional operation. We know that the biggest feature of Java8 is the functional interface. All annotated @functionalInterface interfaces are functional interfaces; specifically, all annotated interfaces will be available on lambda expressions.

There are many interfaces annotated with @functionalInterface, but in this article we will focus on Function, so it is easy to understand the other operations of Function.

@FunctionalInterface
public interface Function<T.R> {
    R apply(T t);
    / * * *@return a composed function that first applies the {@code before}
     * function and then applies this function
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    / * * *@return a composed function that first applies this function and then
     * applies the {@code after} function
     */
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return(T t) -> after.apply(apply(t)); }}Copy the code

To read the source code easily, we need to know something about generics. If you are already familiar with generics, you may want to skip this section.

Generics are a feature introduced in JDK1.5. With generic programming, you can write code that is shared by many different types, which is a great way to improve code reuse. Since the focus of this article is not on generics, we will focus only on the generics implications that the above Function source code requires.

1. A generic class

A generic class uses

to indicate that it is a generic class. Its internal member variables and return values of functions can be generic

. Function source code is identified as

, i.e. two generic parameters, which are not described here.
,r>

2. Generic methods and wildcards

Adding a

to the end of a method modifier indicates that the method is generic, as in the compose method

in the Function source code. The wildcard is also easy to understand, as in the case of compose, where the Functin argument is a Function, and the Functin argument specifies that its first argument must be a parent of V and its second argument must inherit from T, a subclass of T.

The source code parsing

1.apply

With that said, you can start studying the source code.

Function is a generic class that defines two generic parameters T and R. In Function, T represents the input parameter and R represents the returned result. You may wonder why, unlike other Java sources, there is no logic in the Function source code.

Function (x,y) is similar to <T,R>.


y = f ( x ) y=f(x)

So Function has no specific operations that need to be specified, so the exact result returned by apply depends on the lambda expression passed in.

 R apply(T t);
Copy the code

Here’s an example:

public void test(a){
    Function<Integer,Integer> test=i->i+1;
    test.apply(5);
}
/** print:6*/
Copy the code

We define an action with a lambda expression that incremented I by 1. We apply with the argument 5 and return 6. This is different from the way we used to think about Java, where before functional programming we defined a set of operations by first defining a method and then specifying parameters that return the result we want. The idea of functional programming is not to think about specific behaviors, but to think about parameters, which we can set up later.

Here’s another example:

public void test(a){
    Function<Integer,Integer> test1=i->i+1;
    Function<Integer,Integer> test2=i->i*i;
    System.out.println(calculate(test1,5));
    System.out.println(calculate(test2,5));
}
public static Integer calculate(Function<Integer,Integer> test,Integer number){
    return test.apply(number);
}
/** print:6*/
/** print:25*/
Copy the code

We can implement different operations in the same method by passing in different functions. In the actual development, this can greatly reduce a lot of repeated code. For example, I have a function to add users in the actual project, but the users are divided into VIP and ordinary users, and there are two different new logic. So at this point we can write two different kinds of logic. In addition, it separates the logic from the data, so we can reuse the logic.

Of course, the logic in real development can be complicated, for example, both methods F1 and F2 require two logic AB, but F1 requires A->B, and F2 requires B->A. This way we can also use the method just now, the source code is as follows:

public void test(a){
    Function<Integer,Integer> A=i->i+1;
    Function<Integer,Integer> B=i->i*i;
    System.out.println("F1:"+B.apply(A.apply(5)));
    System.out.println("F2:"+A.apply(B.apply(5)));
}
/** F1:36 */
/** F2:26 */

Copy the code

It’s also simple, but it’s not complicated enough. If we need four logic ABCD for F1 and F2, it would be very troublesome for us to write this.

2. com pose and andThen

Compose and andThen solve our problem. Take a look at compose’s source code

  default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
Copy the code

Compose receives a Function argument, returns apply with the incoming logic, and then uses the apply of the current Function.

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
Copy the code

AndThen, as opposed to compose, executes the current logic first andThen the incoming logic.

Maybe that’s not intuitive, but LET me rephrase it for you

Compose is equivalent to a. ply(a.ply (5)), while andThen is equivalent to a. ply(b.ply (5)).

public void test(a){
    Function<Integer,Integer> A=i->i+1;
    Function<Integer,Integer> B=i->i*i;
    System.out.println("F1:"+B.apply(A.apply(5)));
    System.out.println("F1:"+B.compose(A).apply(5));
    System.out.println("F2:"+A.apply(B.apply(5)));
    System.out.println("F2:"+B.andThen(A).apply(5));
}
/** F1:36 */
/** F1:36 */
/** F2:26 */
/** F2:26 */
Copy the code

We can see that the return value of both methods is a Function, which we can use with the Constructor mode operation.

B.compose(A).cpmpose(A).andThen(A).apply(5);
Copy the code

This operation is very simple, you can try it yourself.