6. 新增函数式编程

6.1. 函数式编程简介

函数式编程(functional programming) 或称 函数程序设计,又称 泛函编程,是一种编程典范,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。函数编程语言最重要的基础是λ演算(lambda calculus)。而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值)。比起指令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。这是维基百科给出的定义。从这个我们知道函数式编程是相对于指令式编程的一种编程典范,并且对而言具有一些优点。

函数式编程具有以下三点特性:

 函数是"第一等公民"

什么是"第一等公民"?所谓"第一等公民"(first class),指的是函数与其他数据类型一样,处于平等地位,它不仅拥有一切传统函数的使用方式(声明和调用),可以赋值给其他变量(赋值),也可以作为参数,传入另一个函数(传参),或者作为别的函数的返回值(返回)。函数可以作为参数进行传递,意味我们可以把行为"参数化",处理逻辑可以从外部传入,这样程序就可以设计得更灵活。

 没有"副作用"

所谓"副作用"(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。

 引用透明

引用透明(Referential transparency),指的是函数的运行不依赖于外部变量或"状态",只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。这里强调了一点"输入"不变则"输出"也不变,就像数学函数里面的f(x),只要输入的x一样那得到的结果也肯定定是一样的。

6.2. Java8内置核心函数式接口

 Function<T, R>

函数型接口:有入参,有返回值(表示接受一个参数并产生结果的函数)。
方法:R apply(T t);

 Consumer

消费型接口:有入参,无返回值(表示接受单个输入参数并且不返回结果的操作)。
方法:void accept(T t);

 Supplier

供给型接口:⽆入参,有返回值
方法:T get();

 Predicate

断⾔言型接口:有入参,有返回值,返回值类型确定是boolean。
方法:boolean test(T t);


6.3. 函数式编程Function

Function<T, R>接口源码:

package java.util.function;

import java.util.Objects;

/**
* Represents a function that accepts one argument and produces a result.
* @param <T> the type of the input to the function
* @param <R> the type of the result of the function
* @since 1.8
*/
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);

//其他非泛型T方法、static方法、default方法,这里省略
//其他非泛型T方法、static方法、default方法,这里省略
//其他非泛型T方法、static方法、default方法,这里省略
}

调用:

//Function<Float, Float> fun = (Float a)->a * 200;
Function<Float, Float> fun = a->a * 200;
Float res = fun.apply(10f);
System.out.println(res);//2000.0

6.4. 函数式编程BiFunction

BiFunction<T, U, R>接口源码:

package java.util.function;

import java.util.Objects;

/**
* Represents a function that accepts two arguments and produces a result.
* @param <T> the type of the first argument to the function
* @param <U> the type of the second argument to the function
* @param <R> the type of the result of the function
*
* @see Function
* @since 1.8
*/
@FunctionalInterface
public interface BiFunction<T, U, R> {

/**
* Applies this function to the given arguments.
*
* @param t the first function argument
* @param u the second function argument
* @return the function result
*/
R apply(T t, U u);

//其他非泛型T方法、static方法、default方法,这里省略
//其他非泛型T方法、static方法、default方法,这里省略
//其他非泛型T方法、static方法、default方法,这里省略
}

调用:

public static void main(String[] args) {
// 第一种调用
BiFunction<Double, Double, Double> biFunc = (a, b) -> a + b;
Double res = biFunc.apply(10d, 20d);
System.out.println(res);//30.0

// 第二种调用
Double op1 = operator(10d, 20d, (a, b) -> a + b);
System.out.println(op1);//30.0
Double op2 = operator(10d, 20d, (a, b) -> a / b);
System.out.println(op2);//0.5
}

public static Double operator(Double a, Double b, BiFunction<Double, Double, Double> bf) {
return bf.apply(a, b);
}

6.5. 函数式编程Consumer

Consumer只有入参,没有返回值,常用于打印、发送短信等消费动作。

Consumer接口源码:

package java.util.function;

import java.util.Objects;

/**
* Represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces, {@code Consumer} is expected to operate via side-effects.
* @param <T> the type of the input to the operation
*
* @since 1.8
*/
@FunctionalInterface
public interface Consumer<T> {

/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);

//其他非泛型T方法、static方法、default方法,这里省略
//其他非泛型T方法、static方法、default方法,这里省略
//其他非泛型T方法、static方法、default方法,这里省略
}

应用:

//1.
Consumer<String> consumer = a -> System.out.println(a);
consumer.accept("s");//s

//2.
Consumer<String> consumer2 = a -> {
System.out.println("=======");
System.out.println(a);//abc
};
consumer2.accept("abc");

6.6. 函数式编程Supplier

Supplier没有入参,有返回值,常用于无参工厂方法,即工厂设计模式创建对象。

Supplier接口源码:

package java.util.function;

/**
* Represents a supplier of results.
* @param <T> the type of results supplied by this supplier
* @since 1.8
*/
@FunctionalInterface
public interface Supplier<T> {

/**
* Gets a result.
* @return a result
*/
T get();

//其他非泛型T方法、static方法、default方法,这里省略
//其他非泛型T方法、static方法、default方法,这里省略
//其他非泛型T方法、static方法、default方法,这里省略
}

应用(工厂创建对象):

import java.util.function.Supplier;

public class MySupplier {

public static void main(String[] args) {
Student stu = createStudent();
System.out.println(stu.getName());//默认姓名
}

public static Student createStudent() {
Supplier<Student> supplier = () -> {
Student stu = new Student();
stu.setName("默认姓名");
return stu;
};
return supplier.get();
}

}

class Student {

private String name;

public Student() {

}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

6.7. 函数式编程Predicate

Predicate断言型接口,有入参,有返回值,返回值类型为boolean类型。

Predicate接口源码:

package java.util.function;

import java.util.Objects;

/**
* Represents a predicate (boolean-valued function) of one argument.
* @param <T> the type of the input to the predicate
* @since 1.8
*/
@FunctionalInterface
public interface Predicate<T> {

/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);

//其他非泛型T方法、static方法、default方法,这里省略
//其他非泛型T方法、static方法、default方法,这里省略
//其他非泛型T方法、static方法、default方法,这里省略
}

应用(过滤数据):

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class MyPredicate {

public static void main(String[] args) {

List<String> list = Arrays.asList("apple","banana","orage","strawberry");
List<String> myList1 = myFilter(list, a -> a.startsWith("b"));
System.out.println(myList1);//[banana]

List<String> myList2 = myFilter(list, a -> a.startsWith("b") || a.startsWith("s"));
System.out.println(myList2);//[banana, strawberry]

List<String> myList3 = myFilter(list, a -> {
if(a.length() <= 5){
return true;
}
return false;
});
System.out.println(myList3);//[apple, orage]

}

public static List<String> myFilter(List<String> list, Predicate<String> predicate) {
List<String> results = new ArrayList<>();
for (String str : list) {
if (predicate.test(str)) {
results.add(str);
}
}
return results;
}

}

6.8. 方法与构造函数引用

方法引用 是一种更简洁易懂的lambda表达式,操作符是双冒号​​::​​,用来直接访问类或者实例已存在的方法或构造方法。

语法:
​​​左边是容器(可以是类名,实例名),中间是" :: ",右边是相应的方法名。​

 调用静态方法
ClassName​​​::​​methodName

 调用实例方法
Instance​​​::​​methodName

 调用构造函数
类名​​​::​​new

单个参数的构造函数:

Function<入参1, 返回类型> func = 类名::new;
func.apply(入参);

两个参数的构造函数:

BiFunction<入参1,入参2, 返回类型> func = 类名::new;
func.apply(入参1,入参2);

示例:

import java.util.function.BiFunction;
import java.util.function.Function;

public class Jdk8ApplyConstructor {

public static void main(String[] args) {

// 调用静态方法
Function<String, Integer> func1 = Integer::parseInt;
Integer res1 = func1.apply("12");
System.out.println(res1);// 12

// 调用非静态方法
Function<Integer, String> func2 = "abc"::substring;
String res2 = func2.apply(1);
System.out.println(res2);// bc

// 调用构造函数(单参)
Function<String, User> fun = User::new;
User user1 = fun.apply("张三");
System.out.println(user1.getName());// 张三

// 调用构造函数(多参)
BiFunction<String, Integer, User> bFun = User::new;
User user2 = bFun.apply("王五", 56);
System.out.println(user2.getName() + "," + user2.getAge());// 王五,56

// 函数引用作方法参数
String result = sayHello(String::toUpperCase, "abc");
System.out.println(result);//ABC

}

public static String sayHello(Function<String, String> func, String param) {
String res = func.apply(param);
return res;
}

}

class User {

private String name;
private Integer age;

public User() {

}

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

public User(String name, Integer age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

}