Java’s functional programming is like this!

Time:2021-1-25

introduce

Java language is an object-oriented programming language, which has nothing to do with functional programming. Starting with the lambda expression and other new features added from Java 8, Java also began to support functional programming.

In functional programming, function is the first class citizen.

Let’s take a look at the call before and after Java 8.RunnableThread comparison:

class Printer {

Output results:

Anonymous
lambda
Printer::print()

Lambda

Lambda expression is a new feature of Java 8, which is used to implement functional programming. The bottom layer is generated by anonymous class. Lambda expression is essentially an anonymous method, but this method is not executed independently, but is used to implement another method defined by functional interface. Therefore, the lambda expression causes an anonymous class to be generated.

The basic syntax of lambda expression is as follows:(parameters) -> expressionperhaps(parameters) -> {statements;}

  1. parameters: similar to the formal parameter list in the method, the parameters here are the parameters in the functional interface. The parameter type here can be displayed, specified or not declared, because the parameter type can be inferred very often. When there is only one inferred type, the parenthesis can be omitted.
  2. next->It can be understood as “used for”.
  3. ->The following contents are method bodies, which can be expressions or code blocks to implement methods in functional interfaces. This method body can have a return value or no return value.

When lambda expression is single line, the return value is generated automatically, and it is illegal to use return; when it is multi line, it must be enclosed with {} and return.

interface Console {

Output results:

Log info [test lambda expression with parameter and no return value]

closure

Lambda expressions can access static variables, instance variables of a class, and variables that are calledfinalModify the local variables. There is no difference between accessing static variables and instance variables. Only when accessing local variables, they must be accessedfinalAfter modification, it has not been changedfinalWhen lambda expression calls the modified local variable, it will also be implicitly declared as modifiedfinalThe constant of the modifier.

public class LambdaScopes {

Functional interface

We all know that lambda expressions can only be used by calling interfaces, but not all interfaces can use lambda expressions. The interface that lambda expression calls must have only one abstract method, which is functional interface.

However, there can be multiple non abstract methods in functional interfaces, which is a new feature of Java 8: being useddefaultThe default method of decoration.

Java 8 adds a new annotation@FunctionalInterface: the interface decorated by this annotation enforces the “functional method” pattern, that is, there is only one abstract method.

@FunctionalInterface

@FunctionalInterfaceAnnotations are optional; for example, in JDKRunnableInterfaces are also functional interfaces.

public interface Runnable {
    void run();
}

When there are multiple methods in the interface, use@FunctionalInterfaceAnnotations cause compile time errors in the interface.

Java's functional programming is like this!

The above picture will report an error message at compile time:Multiple non-overriding abstract methods found in interface xx.xx.Converter

In Java, the implementation of any object is inherited by defaultObjectClass, the functional interface is not unexpected, so thejava.lang.ObjectInpublicMethods are included by defaultObjectClass.

It completely conforms to the definition of functional interface

@FunctionalInterface

Common functional interfaces

Predicate

PredicateThe abstract method in the interface is used for condition judgment. The interface is as follows:

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

We are rightPredicateImplementation of an example to illustratetest()Method to determine whether the character D exists in the string

Predicate<String> p1 = s -> s.contains("d");
boolean b1 = p1.test("Hello World!");

PredicateThere are three default methods in conditional judgment: and, or, and not

//And in logical relation

Use of the above three default methods:

Predicate<String> p1 = s -> s.contains("d");

PredicateThere are also two static methods in

//Determine whether two objects are equal
Function

java.util.function.Function<T,R>The interface is as follows:

@FunctionalInterface

Convert a numeric type to a string type as shown in the following example:

Function<Number, String> function = String::valueOf;
String apply = function.apply(12);

FunctionThere are two default methods in the interface, which are used for composition operations.

compose()The source code of the method is as follows:

default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {

andThen()The source code is as follows:

//Here, one V is used as input value and the other as output value. According to the calling order, the order of output is different for T V input

There is also a static methodidentity()The source code is as follows:

//Returns a function object that only returns input parameters after the apply() method is executed

Call itself, the input object is the output object.

According toFunctionThe implementation of methods in functional interface is as follows:

public class FunctionTest {
Consumer

java.util.function.Consumer<T>Interface is used to receive a generic object with no return value. The source code is as follows:

@FunctionalInterface

It also has a default method:

default Consumer<T> andThen(Consumer<? super T> after) {
    Objects.requireNonNull(after);
    return (T t) -> { accept(t); after.accept(t); };
}

The purpose of this default method is to make twoConsumerRealize the combination operation.

public class ConsumerTest {
Supplier

java.util.function.Supplier<T>Interface is used to get the object data of the type specified by a generic parameter

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

Because it is a functional interface, it means that the corresponding lambda expression needs to “externally provide” the object data of a service generic type.

public class SupplierTest {

There are many extension functions, but we have mastered the above several basic interfaces, and the use of other interfaces is basically the same.

java.util.functionPackage is designed to create a complete set of target interfaces, so that we generally do not need to define our own interfaces. This is mainly because the base type generates a small number of interfaces. If you understand naming patterns, as the name suggests, you know what a particular interface does.

Higher order function

One of the characteristics of functional programming is high-order function.

In “functional” language, “function” is the “first citizen”, that is to say, function can be like integer, floating-point number, substring, etc., as function parameters, member variables and return values.

The function in Java is not the first citizen, but the interface of a virtual member function.

Higher order function means that the parameter is a function, or the return value is a function.

The following is an example of a high-order function, which uses the local variable domain property to perform delayed evaluation:

public class HigherOrderFunctionTest {

Method reference

Lambda expressions also provide a simpler method call, which is method reference.

Method reference composition: class name or object name, followed by::, followed by the method name.

public class MethodReferences {
    static class Description {
        String about;
        Description(String desc) {
            about = desc;
        }
        void help(String msg) {
            System.out.println(about + " " + msg);
        }
    }
    public static void main(String[] args) {
        Consumer<String> c = i -> {
            Description a = new Description("Valuable");
            a.help(i);
        };
        c.accept("information");
    }
}

Here we use method references instead of lambda expressions

Consumer<String> c = new Description("Valuable")::help;
c.accept("information");

therenew Description("Valuable")::helpIt can be seen as a short form of lambda expression. Although method references don’t necessarily make the syntax more compact, they have more explicit semantics – if we want to call a method with a name, we can call it directly by its name.

classification

Method references can be divided into the following categories:

  • Method reference of static method;
  • Method reference of instance method;
  • Method reference of instance object;
  • The method reference that constructs the method.
Method references for static methods
class Logger {

Through lambda expression, call its static method:

Consumer<String> c = s -> Logger.info(s);
c.accept("static method reference")

The above static method call can use method reference:

Consumer<String> c = Logger::info;
c.accept("static method reference");
Method reference of instance method
class Length {
    private Integer size;
    public Length(Integer size) { this.size = size; }
    int compore(Length o) { return this.size.compareTo(o.size); }
    @Override
    public String toString() {
        return "Length{" + "size=" + size + '}'; 
    }
}

Call its example method through lambda expression:

Arrays.sort(lengths, (l1, l2) -> l1.compore(l2));

The above instance method call can be changed to the method reference of the instance method:

Arrays.sort(lengths, Length::compore);
Method reference of instance object
class Describe {
    void show(String msg) {
        System.out.println(msg);
    }
}

Call its example method through lambda expression:

Describe d = new Describe();
Consumer<String> c = (a) -> d.show(a);
c.accept("instance method reference");

The above expression can be changed to the method reference of instance object:

Consumer<String> c = new Describe()::show;
c.accept("instance object method reference");
Method reference of construction method

You can also capture a reference to a constructor and call it by reference.

class Student {

Output results:

Person[name='Jan', age=-1]
Person[name='Feb', age=-1]
Person[name='Mar', age=24]

StudentThere are three constructors, themake()Method reflects the list of constructor parameters.

It’s all used on itStudent::newBecause the constructor has only::newMethod reference. The compiler can detect and assign the constructor to the corresponding interface for calling.

summary

The lambda expression newly added in Java 8 is more in line with the functional direction of today’s programming language, and also makes the code concise, but it is not easy to maintain in the later stage, and the readability is poor.

More content, please pay attention to the official account.Hairen’s blog“, reply to” resources “to get free learning resources!

Java's functional programming is like this!