# 函数式编程
一种 语法优雅、简洁健壮、高并发、易于测试和调试 的编程方式,这就是函数式编程 (FP) 的意义所在。
OO (Object oriented, 面向对象) 是抽象数据
FP (Function programming, 函数式编程) 是抽象行为
两个关键概念:
- 函数是第一等公民
- 函数要满足以下约束
- 函数的返回值仅取决于传递给函数的输入参数
- 函数的执行没有副作用
# 函数是一等公民
在函数式编程范式中,函数是语言的第一等公民。这意味着可以创建函数的 “实例”,对函数实例的变量引用,就像对字符串、Map 或者任何其他对象的引用一样。函数也可以作为参数传递给其他函数。
在 Java 中,函数显然不是第一等公民,类 (Class) 才是。所以 Java 才引用 Lambda 表达式,这个语法糖从表现层让 Java 拥有了函数,让函数可以作为变量的引用、方法的参数等等。(为什么说是从表现层呢?因为实际上,在编译的时候,Java 编译器还是会把 Lambda 表达式编译成类。
# 纯函数
函数式编程中,有个纯函数 (Pure Function) 的概念,如果一个函数满足以下条件,才是纯函数:
- 该函数的执行没有副作用
- 函数的返回值仅取决于传递给函数的输入参数
public class ObjectWithPureFunction{
public int sum(int a, int b){
return a+b;
}
}
# 非纯函数
上面的 sum 方法的返回值仅取决于其输入参数,而且 sum 是没有副作用的,它不会在任何地方修改函数之外的任何状态 (变量)
相反,这个一个非纯函数的例子:
public class ObjectWithNonPureFunction{
private int value = 0;
public int add(int nextValue) {
this.value += nextValue;
return this.value;
}
}
add 方法使用成员变量 value 来计算返回值,并且它还修改了 value 成员变量,这代表它有副作用,这两个条件都导致 add 方法不是一个纯函数
# 函数式接口
所谓函数式接口,实际上就是接口里面有且只能有一个抽象方法的接口。
函数式接口也称为 单一抽象方法 (SAM) 接口
# 函数式接口的特点
- 接口有且仅有一个抽象方法,如接口 Comparator
- 允许出现 java.lang.Object 中的 public 方法,如 equals
- 允许定义静态非抽象方法
- 允许定义默认 default 非抽象方法 (default 方法式 java8 开始出现的)
- FunctionInterface 注解不是必须的,如果一个接口符合 “函数式接口” 的定义,那么不加该注解也没有影响
甚至可以说:函数式接口是专门为 lambda 表达式准备的,lambda 表达式是只实现接口中唯一抽象方法的匿名实现类
# default 关键字
在 java8 之前
- 接口是不能有方法的实现,所有的方法必须是抽象方法
- 实现接口就必须实现接口里面的所有方法
这样导致:当一个接口有很多实现类的时候,修改这个接口,就必须修改所有的实现类。
不过在 java8 中这个问题得到解决,这就是 default 方法
- default 方法可以有自己的默认实现,即有方法体
- 接口实现类可以不去实现 default 方法,并且可以使用 default 方法
# JDK 中函数式接口的举例
- java.lang.Runnable,
- java.util.Comparator,
- java.util.concurrent.Callable
- java.util.function 包下的接口,如 Consumer、Predicate、Supplier 等
编译器会根据 Lambda 表达式的参数和返回值类型推断出其实现的抽象方法,进而推断出其实现的接口,如果一个接口有多个抽象方法,显然是没有办法用 Lambda 表达式实现该接口的
# @FunctionInterface 注解
标注接口是一个函数式接口的注解
@FunctionalInterface // 标明接口为函数式接口
public interface MyInterface {
public void run(); //抽象方法
}
一旦使用了该注解标注接口,Java 的编译器会强制检查该接口是否满足函数式接口的要求:“确实仅有一个抽象方法”,否则将会报错
需要注意的是,及时不适用该注解,只要有一个接口满足函数式接口的要求,那它仍然是一个函数式接口,使用起来都一样。该注解只起到标记接口指示编译器对其进行检查的作用
# Java 内置的函数式接口
Function
Function 接口(全限定名:java.util.function.Function)是 Java 中最核心的函数式接口。 Function 接口表示一个接受单个参数并返回单个值的函数(方法)
Predicate
Predicate 接口 (全限定名:java.util.function.Predicate)表示一个接收单个参数,并返回布尔值 true 或 false 的函数
Supplier
Supplier 接口(java.util.function.Supplier),表示提供某种值的函数
Consume
Consumer 接口(java.util.function.Consume)表示一个函数,该函数接收一个参数,但是不返回任何值
Optional
Optional 接口并不是一个函数式接口,它常在 Stream 操作中出现,作为操作的返回值类型。
Optional 接口是预防 NullPointerException 的好工具,它是一个简单的容器,其值可以是 null 或非 null。比如一个可能返回一个非空结果的方法,方法在有些情况下返回值,有些情况不满足返回条件返回空值,这种情况下使用 Optional 接口作为返回类型,比直接无值时返回 Null 要更安全
# Lambda 表达式
# 双冒号语法
其实双冒号就是 lambda 表达式的简写。被用作方法的引用。使用 lambda 表达式会创建匿名方法,但是有时候会需要一个 lambda 表达式只调用一个已经存在的方法,在此才有了方法的引用
- 静态方法的引用 (static method)
public class Demo {
@Test
public void test() {
List<String> list = Arrays.asList("aaaa", "bbbb", "cccc");
//静态方法语法 ClassName::methodName
list.forEach(Demo::print);
}
public static void print(String content){
System.out.println(content);
}
}
- 对象实例方法的引用
public class Demo {
@Test
public void test() {
List<String> list = Arrays.asList("aaaa", "bbbb", "cccc");
//静态方法语法 ClassName::methodName
list.forEach(new Demo()::print);
}
public void print(String content){
System.out.println(content);
}
}
- 对象的超类方法引用
public class Example extends BaseExample{
@Test
public void test() {
List<String> list = Arrays.asList("aaaa", "bbbb", "cccc");
//对象的超类方法语法: super::methodName
list.forEach(super::print);
}
}
class BaseExample {
public void print(String content){
System.out.println(content);
}
}
- 类构造器引用
public class Example {
@Test
public void test() {
InterfaceExample com = Example::new;
Example bean = com.create();
Example bean = com.create("STRING");
System.out.println(bean);
}
}
interface InterfaceExample{
//不带参数
Example create();
//带参数
Example create(String str);
}