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;
}
}