Java functional programming and lambda expressions explained

Author: DemonsI

my.oschina.net/demons99/blog/2223079

Why use functional programming

Functional programming is more often a programming way of thinking and methodology. The main difference between functional and imperative programming is that functional programming tells the code what you want to do, while imperative programming tells the code how to do it. To put it bluntly, functional programming is based on a certain syntax or calling API to program.

For example, we now need to find the smallest number from a set of numbers. If we use imperative programming to achieve this requirement, the code written is as follows:

public static void main(String[] args) {
    int[] nums = new int[]{1, 2, 3, 4, 5, 6, 7, 8};

    int min = Integer.MAX_VALUE;
    for (int num : nums) {
        if (num < min) {
            min = num;
        }
    }
    System.out.println(min);
}

If you use functional programming to implement it, the code you write is as follows:

public static void main(String[] args) {
    int[] nums = new int[]{1, 2, 3, 4, 5, 6, 7, 8};

    int min = IntStream.of(nums).min().getAsInt();
    System.out.println(min);
}

From the above two examples, it can be seen that imperative programming needs to implement specific logic details by itself. Functional programming is to call API to complete the realization of requirements. The original imperative code is written into a series of nested function calls. Under functional programming, the code is more concise and easy to understand. This is why functional programming is used. one of the reasons. That's why it is said that functional programming is to tell the code what you want to do, while imperative programming is to tell the code what to do, which is a change of thinking.

Speaking of functional programming, we have to mention lambda expressions, which are the basis of functional programming. When Java does not support lambda expressions, if we need to create a thread, we need to write the following code:

public static void main(String[] args) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("running");
        }
    }).start();
}

The use of lambda expressions can complete the creation of a thread with one sentence of code. Lambda emphasizes the input and output of the function, hides the details of the process, and can accept functions as input (parameters) and output (return values):

public static void main(String[] args) {
    new Thread(() -> System.out.println("running")).start();
}

Note: The left side of the arrow is the input, and the right is the output

The function of the lambda expression is actually to return the implementation object of the Runnable interface, which is similar to calling a method to obtain the instance object, except that the implementation code is directly written in the lambda expression. We can make a simple comparison:

public static void main(String[] args) {
    Runnable runnable1 = () -> System.out.println("running");
    Runnable runnable2 = RunnableFactory.getInstance();
}

New features of JDK8 interface

1. Function interface, the interface can only have one method that needs to be implemented, which can @FunctionalInterface be declared using annotations. as follows:

@FunctionalInterface
interface Interface1 {
    int doubleNum(int i);
}

Several ways of using lambda expressions to obtain implementation instances of this interface:

public static void main(String[] args) {
    // 最常见的写法
    Interface1 i1 = (i) -> i * 2;
    Interface1 i2 = i -> i * 2;

    // 可以指定参数类型
    Interface1 i3 = (int i) -> i * 2;

    // 若有多行代码可以这么写
    Interface1 i4 = (int i) -> {
        System.out.println(i);
        return i * 2;
    };
}

2. A more important interface feature is the default method of the interface, which is used to provide a default implementation. The default method is the same as the method of the ordinary implementation class, you can use keywords such as this:

@FunctionalInterface
interface Interface1 {
    int doubleNum(int i);

    default int add(int x, int y) {
        return x + y;
    }
}

The reason why the feature of default method is more important is that we can use this feature to provide default implementation on some previously written interfaces, and will not affect any implementation classes and existing code. For example, the List interface we are most familiar with, the List interface has not changed any code since JDK1.2, and only after 1.8 did this new feature add some default implementations. This is because if there is no feature of the default method, the impact of modifying the interface code is huge, and with the default method, adding the default implementation can not affect any code.

3. When the interface is multiple inherited, the problem of default method coverage may occur. At this time, you can specify which interface default method implementation to use, as shown in the following example:

@FunctionalInterface
interface Interface1 {
    int doubleNum(int i);

    default int add(int x, int y) {
        return x + y;
    }
}

@FunctionalInterface
interface Interface2 {
    int doubleNum(int i);

    default int add(int x, int y) {
        return x + y;
    }
}

@FunctionalInterface
interface Interface3 extends Interface1, Interface2 {

    @Override
    default int add(int x, int y) {
        // 指定使用哪一个接口的默认方法实现
        return Interface1.super.add(x, y);
    }
}

Function interface

In this section, let's take a look at the important function interfaces that come with JDK8:

You can see that there are several interfaces in the above table, and the most commonly used one is the Function interface, which saves us from defining some unnecessary function interfaces and reduces the number of interfaces. We use a simple example to demonstrate the use of the Function interface:

import java.text.DecimalFormat;
import java.util.function.Function;

class MyMoney {
    private final int money;

    public MyMoney(int money) {
        this.money = money;
    }

    public void printMoney(Function<Integer, String> moneyFormat) {
        System.out.println("我的存款: " + moneyFormat.apply(this.money));
    }
}

public class MoneyDemo {
    public static void main(String[] args) {
        MyMoney me = new MyMoney(99999999);

        Function<Integer, String> moneyFormat = i -> new DecimalFormat("#,###").format(i);
        // 函数接口支持链式操作,例如增加一个字符串
        me.printMoney(moneyFormat.andThen(s -> "人民币 " + s));
    }
}

Run the above example, the console output is as follows:

我的存款: 人民币 99,999,999

If the Function interface is not used in this example, you need to define a function interface yourself, and chain operation is not supported, as in the following example:

import java.text.DecimalFormat;

// 自定义一个函数接口
@FunctionalInterface
interface IMoneyFormat {
    String format(int i);
}

class MyMoney {
    private final int money;

    public MyMoney(int money) {
        this.money = money;
    }

    public void printMoney(IMoneyFormat moneyFormat) {
        System.out.println("我的存款: " + moneyFormat.format(this.money));
    }
}

public class MoneyDemo {
    public static void main(String[] args) {
        MyMoney me = new MyMoney(99999999);

        IMoneyFormat moneyFormat = i -> new DecimalFormat("#,###").format(i);
        me.printMoney(moneyFormat);
    }
}

Then let's take a look at the use of the Predicate interface and the Consumer interface, as shown in the following example:

public static void main(String[] args) {
    // 断言函数接口
    Predicate<Integer> predicate = i -> i > 0;
    System.out.println(predicate.test(-9));

    // 消费函数接口
    Consumer<String> consumer = System.out::println;
    consumer.accept("这是输入的数据");
}

Run the above example, the console output is as follows:

false
这是输入的数据

These interfaces generally have basic types of encapsulation, and there is no need to specify generics when using specific types of interfaces. The following example:

public static void main(String[] args) {
    // 断言函数接口
    IntPredicate intPredicate = i -> i > 0;
    System.out.println(intPredicate.test(-9));

    // 消费函数接口
    IntConsumer intConsumer = (value) -> System.out.println("输入的数据是:" + value);
    intConsumer.accept(123);
}

Run the above code, the console output is as follows:

false
输入的数据是:123

With the foreshadowing of the above interface example, we should have a preliminary understanding of the use of the function interface, and then we demonstrate the use of the remaining function interface:

public static void main(String[] args) {
    // 提供数据接口
    Supplier<Integer> supplier = () -> 10 + 1;
    System.out.println("提供的数据是:" + supplier.get());

    // 一元函数接口
    UnaryOperator<Integer> unaryOperator = i -> i * 2;
    System.out.println("计算结果为:" + unaryOperator.apply(10));

    // 二元函数接口
    BinaryOperator<Integer> binaryOperator = (a, b) -> a * b;
    System.out.println("计算结果为:" + binaryOperator.apply(10, 10));
}

Run the above code, the console output is as follows:

提供的数据是:11
计算结果为:20
计算结果为:100

The BiFunction interface has one more input than the Function interface. The following example:

class MyMoney {
    private final int money;
    private final String name;

    public MyMoney(int money, String name) {
        this.money = money;
        this.name = name;
    }

    public void printMoney(BiFunction<Integer, String, String> moneyFormat) {
        System.out.println(moneyFormat.apply(this.money, this.name));
    }
}

public class MoneyDemo {
    public static void main(String[] args) {
        MyMoney me = new MyMoney(99999999, "小明");

        BiFunction<Integer, String, String> moneyFormat = (i, name) -> name + "的存款: " + new DecimalFormat("#,###").format(i);
        me.printMoney(moneyFormat);
    }
}

Run the above code, the console output is as follows:

小明的存款: 99,999,999

Method reference

After learning about lambda expressions, we usually use lambda expressions to create anonymous methods. But sometimes we just need to call an existing method. The following example:

Arrays.sort(stringsArray, (s1, s2) -> s1.compareToIgnoreCase(s2));

In jdk8, we can use a new feature to shorten this lambda expression. The following example:

Arrays.sort(stringsArray, String::compareToIgnoreCase);

This feature is called Method Reference. The method is referenced standard form: 类名::方法名. (Note: You only need to write the method name, not the parentheses).

There are currently four forms of method reference:

Below we use a simple example to demonstrate several ways of writing method references. First define an entity class:

public class Dog {
    private String name = "二哈";
    private int food = 10;

    public Dog() {
    }

    public Dog(String name) {
        this.name = name;
    }

    public static void bark(Dog dog) {
        System.out.println(dog + "叫了");
    }

    public int eat(int num) {
        System.out.println("吃了" + num + "斤");
        this.food -= num;
        return this.food;
    }

    @Override
    public String toString() {
        return this.name;
    }
}

Call the method in the entity class by method reference, the code is as follows:

package org.zero01.example.demo;

import java.util.function.*;

/**
 * @ProjectName demo
 * @Author: zeroJun
 * @Date: 2018/9/21 13:09
 * @Description: 方法引用demo
 */
public class MethodRefrenceDemo {

    public static void main(String[] args) {
        // 方法引用,调用打印方法
        Consumer<String> consumer = System.out::println;
        consumer.accept("接收的数据");

        // 静态方法引用,通过类名即可调用
        Consumer<Dog> consumer2 = Dog::bark;
        consumer2.accept(new Dog());

        // 实例方法引用,通过对象实例进行引用
        Dog dog = new Dog();
        IntUnaryOperator function = dog::eat;
        System.out.println("还剩下" + function.applyAsInt(2) + "斤");

        // 另一种通过实例方法引用的方式,之所以可以这么干是因为JDK默认会把当前实例传入到非静态方法,参数名为this,参数位置为第一个,所以我们在非静态方法中才能访问this,那么就可以通过BiFunction传入实例对象进行实例方法的引用
        Dog dog2 = new Dog();
        BiFunction<Dog, Integer, Integer> biFunction = Dog::eat;
        System.out.println("还剩下" + biFunction.apply(dog2, 2) + "斤");

        // 无参构造函数的方法引用,类似于静态方法引用,只需要分析好输入输出即可
        Supplier<Dog> supplier = Dog::new;
        System.out.println("创建了新对象:" + supplier.get());

        // 有参构造函数的方法引用
        Function<String, Dog> function2 = Dog::new;
        System.out.println("创建了新对象:" + function2.apply("旺财"));
    }
}

Type inference

Through the above examples, we know that the reason why Lambda expressions can be used is that there must be a corresponding functional interface. This is consistent with Java's strongly typed language, which means you can't write Lambda expressions arbitrarily anywhere in the code. In fact, the type of Lambda is the type of the corresponding functional interface. Another basis for lambda expressions is the type inference mechanism. When the context information is sufficient, the compiler can infer the type of the parameter table without the need for explicit naming.

So the type of Lambda expression is inferred from the context of Lambda, the type required by Lambda expression in the context is called the target type, as shown in the following figure:

Next, we use a simple example to demonstrate several type inferences of Lambda expressions. First, we define a simple functional interface:

@FunctionalInterface
interface IMath {
    int add(int x, int y);
}

The sample code is as follows:

public class TypeDemo {

    public static void main(String[] args) {
        // 1.通过变量类型定义
        IMath iMath = (x, y) -> x + y;

        // 2.数组构建的方式
        IMath[] iMaths = {(x, y) -> x + y};

        // 3.强转类型的方式
        Object object = (IMath) (x, y) -> x + y;

        // 4.通过方法返回值确定类型
        IMath result = createIMathObj();

        // 5.通过方法参数确定类型
        test((x, y) -> x + y);

    }

    public static IMath createIMathObj() {
        return (x, y) -> x + y;
    }

    public static void test(IMath iMath){
        return;
    }
}

Variable reference

Lambda expressions are similar to internal classes or anonymous classes that implement a specified interface, so the rules for referencing variables in Lambda expressions are the same as when we refer to variables in anonymous classes. The following example:

public static void main(String[] args) {
    String str = "当前的系统时间戳是: ";
    Consumer<Long> consumer = s -> System.out.println(str + s);
    consumer.accept(System.currentTimeMillis());
}

It is worth mentioning that before JDK1.8 we generally set the external variables accessed in the anonymous class to final, and in JDK1.8 the external variables accessed in the anonymous class are set to final by default. For example, if I change the value of the str variable now, ide will prompt an error:

As for why the variables are set to final, this is because there is no passing by reference in Java, and variables are passed by value. If the variable is not set to final, if the reference of the external variable is changed, the final result will be wrong.

Let's use a set of pictures to briefly demonstrate the difference between value passing and reference passing. Take the list as an example. When it's just value passing, the reference to the external variable in the anonymous class is a value object:

If the list variable points to another object at this time, then the value object in the anonymous class is still the previous value object, so we need to set it to final to prevent external variable references from changing:

If it is passed by reference, the reference to the external variable in the anonymous class is not a value object, but a pointer to this external variable:

So even if the list variable points to another object, the reference in the anonymous class will change as the reference of the external variable changes:

Cascading expressions and currying

In functional programming, functions can either receive or return other functions. A function is no longer like in traditional object-oriented programming, it is just an object factory or generator, and it can also create and return another function. Functions that return functions can be turned into cascading lambda expressions. It is particularly worth noting that the code is very short. Although this syntax may seem very unfamiliar at first, it has its own purpose.

A cascade expression is a combination of multiple lambda expressions. This involves the concept of a higher-order function. The so-called higher-order function is a function that can return a function. The following example:

// 实现了 x + y 的级联表达式
Function<Integer, Function<Integer, Integer>> function1 = x -> y -> x + y;
System.out.println("计算结果为: " + function1.apply(2).apply(3));  // 计算结果为: 5

Here  y -&gt; x + y is returned as a function to the upper level expression, so the output of y -&gt; x + ythe first level expression is  this function. It may be easier to understand if you use parentheses:

x -> (y -> x + y)

Cascading expressions can realize function currying. Simply put, currying is to convert a function with multiple parameters into a function with only one parameter, as shown in the following example:

Function<Integer, Function<Integer, Function<Integer, Integer>>> function2 = x -> y -> z -> x + y + z;
System.out.println("计算结果为: " + function2.apply(1).apply(2).apply(3));  // 计算结果为: 6

The purpose of function currying is to standardize functions, which can be flexibly combined to facilitate unified processing, etc. For example, I can only call the same method in the loop without calling another method to achieve the element in an array. And calculation, the code is as follows:

public static void main(String[] args) {
    Function<Integer, Function<Integer, Function<Integer, Integer>>> f3 = x -> y -> z -> x + y + z;
    int[] nums = {1, 2, 3};
    for (int num : nums) {
        if (f3 instanceof Function) {
            Object obj = f3.apply(num);
            if (obj instanceof Function) {
                f3 = (Function) obj;
            } else {
                System.out.println("调用结束, 结果为: " + obj);  // 调用结束, 结果为: 6
            }
        }
    }
}

Cascading expressions and currying are generally not very common in actual development, so a little understanding of their concepts is enough, here is just a brief introduction, if you are interested in them, you can refer to related materials.

END

Friends who like this article, please click on the picture to follow the subscription account and watch more exciting content!

Recommended reading:

我知道你 “在看”

Guess you like

Origin blog.csdn.net/qq_39507327/article/details/111026994