从历史上看,用 Java 进行函数式编程并不容易,甚至一些函数式编程在 Java 中是不可能实现的。在 Java 8 , Oracle 做了一些努力使得函数式编程变得更容易,这些努力在某种程度上取得了成功。在这个 Java 函数式编程教程中将介绍基本的函数式编程,以及 Java 中可以实现的部分。
函数式编程基础
函数式编程包括以下几个关键的概念:
- 函数是第一类对象
- 纯函数
- 高阶函数
纯函数编程也有一套规则需要遵守:
- 无状态
- 无副作用
- 不可变变量
- 优先使用递归而不是循环
这些概念和规则将会在这篇教程的剩余部分一一介绍。
尽管如果你一直没有遵守这些规则,你同样可以从函数式编程中获取收获并应用在你的应用中。你将会看到函数式编程并不是解决所有问题的正确工具,特别是 “无副作用” 使得写入数据库(这是个副作用)变得很困难。你需要了解哪些问题函数式编程擅长解决,哪些不是。
函数是第一类对象
在函数式编程范式中,函数是语言里第一类对象。这意味着你可以创建一个函数的 “实例”,作为一个函数实例的引用,就像 String、 Map 和其它对象的引用一样。函数同样可以当做参数传递给其它函数。
在 Java 里,方法 不是第一类对象。 最接近的是 Java Lamda 表达式,在这里我不讨论这个。
纯函数
一个函数如果是一个 纯函数,需要满足以下条件:
- 函数的执行不产生副作用
- 函数的返回值只依赖入参
下面是一个 Java 里的 纯函数 例子:
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
成员变量的值,因此他有副作用。
高阶函数
一个函数如果是高阶函数则至少要满足以下条件之一:
- 使用一个或多个函数作为参数
- 函数返回另外一个函数作为结果
在 Java 中,最接近高阶函数的是:一个函数(方法)使用一个或多个 lambda 表达式作为参数,以及返回另外一个 lambda 表达式。下面就是一个 Java 里 高阶函数 的例子:
public class HigherOrderFunctionClass {
public IFactory createFactory(IProducer producer, IConfigurator configurator) {
return () -> {
T instance = producer.produce();
configurator.configure(instance);
return instance;
}
}
}
注意 :1. createFactory()
方法返回一个 lambda 表达式作为返回值。这是作为 函数式编程的一个条件。
2. createFactory()
方法使用两个接口(IProducer
and Iconfigurator
)的实现的实例作为参数. Java lambda 表达式需要实现一个 函数式接口,记得吗?
想象一下接口是这样:
public interface IFactory {
T create();
}
public interface IProducer {
T produce();
}
public interface IConfigurator {
void configure(T t);
}
你可以看到,这些接口都是函数式接口。因此他们可以被 Java lamda 表达式所实现 —— 所以 createFactory
方法是一个 高阶函数
无状态
像在教程开头提到的,函数式编程范式的规则之一就是无状态。 "无状态" 通常指函数之外没有状态。一个函数可以拥有包含内部临时状态的本地变量,但是这个函数不能引用任何该函数所属类或对象的成员变量。
下面是一个没有使用外部状态的例子:
public class Calculator {
public int sum(int a, int b) {
return a + b;
}
}
相反, 下面是一个使用外部状态的例子
public class Calculator {
private int initVal = 5;
public int sum(int a) {
return initVal + a;
}
}
这个函数违背了 "无状态" 规则
无副作用
函数式范式另外一个规则就是 "无副作用"。这意味着,一个函数不能修改任何函数之外的状态。改变函数之外的状态被称作一个 副作用。
函数外部状态有:函数所属类或对象的成员变量,函数参数中的成员变量,或者外部系统的状态比如文件系统或者数据库。
不可变变量(Immutable Variables)
函数式编程范式的第三个规则是 “不可变变量”。不可变变量有利于防止产生副作用。
优先使用递归而不是循环
函数式编程范式的第四个规则是“优先使用递归而不是循环”。递归使用函数调用来达到循环的目的,因此代码变得更函数式。
循环的另一个代替是 Java Stream API. 这个 API 是受 函数式启发的。
函数式接口
函数式接口在 Java 里是只拥有一个抽象方法的接口。一个抽象方法意味着只有一个方法没有实现。一个接口可以有多个方法比如默认方法和静态方法——两个都有实现,但只要这个接口只有一个没有实现的接口,这个接口就叫做 函数式接口
下面是一个函数式的例子:
public interface MyInterface {
public void run();
}
下面是另外一个携带默认方法和静态方法实现的函数式接口:
public interface MyInterface2 {
public void run();
public default void doIt() {
System.out.println("doing it");
}
public static void doItStatically() {
System.out.println("doing it statically");
}
}
注意这两个拥有实现的方法。这仍是一个 函数接口,因为只有 run()
方法没有被实现。可是如果再有一个没有实现的方法,这个接口就不再是函数式接口,不能被 Java lambda 表达式所实现。
译自: Java Functional Programming