# 函数式编程

一种 语法优雅、简洁健壮、高并发、易于测试和调试 的编程方式,这就是函数式编程 (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 表达式只调用一个已经存在的方法,在此才有了方法的引用

  1. 静态方法的引用 (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);
	}
}
  1. 对象实例方法的引用

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);
	}
}
  1. 对象的超类方法引用
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);
	}
}
  1. 类构造器引用

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