Java 函数式编程

从 Java 8 开始,便拥有了函数式编程的能力。这个能力通俗来讲,就是可以把一个方法实现(也可简单的理解为代码片段)作为参数进行传递,并在适当的时候执行。

Java 有内置的一些类或工具就使用到这个特性,例如整理集合时所使用的stream()

其实在真实的项目开发中,使用这个特性的人并不多。然而在一次偶然的开发过程中,让我深刻理解到这个特性的重要程度,以及它能为代码结构带来多大的优化空间。

为什么要使用它?

抛出问题

我们来看一个代码场景,现在有一个Procedure类,如下

1
2
3
4
5
6
7
8
9
10
11
public class Procedure {
public static Integer test1(Integer t) {
return t + 1;
}
public static Integer test2(Integer t) {
return t + 2;
}
public static Integer test3(Integer t) {
return t + 3;
}
}

还有一个Share类,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Share {
public static void a() {
for (int i = 0; i <= 3; i++) {
Integer value = Procedure.test1(i);
System.out.println("代码第" + i + "次执行结果:" + value);
}
}
public static void b() {
for (int i = 0; i <= 3; i++) {
Integer value = Procedure.test2(i);
System.out.println("代码第" + i + "次执行结果:" + value);
}
}
public static void c() {
for (int i = 0; i <= 3; i++) {
Integer value = Procedure.test3(i);
System.out.println("代码第" + i + "次执行结果:" + value);
}
}
}

想想怎么优化Share类里的a() b() c()方法,提高代码的复用性,优化不允许变更a() b() c()方法的功能。

一般优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Share {
public static void a() {
for (int i = 0; i <= 3; i++) {
Integer value = Procedure.test1(i);
println(i, value);
}
}
public static void b() {
for (int i = 0; i <= 3; i++) {
Integer value = Procedure.test2(i);
println(i, value);
}
}
public static void c() {
for (int i = 0; i <= 3; i++) {
Integer value = Procedure.test3(i);
println(i, value);
}
}
public static void println(Integer index, Integer value) {
System.out.println("代码第" + index + "次执行结果:" + value);
}
}

仿佛最大的优化,也只能把打印结果的那一行代码抽出,作为公共的方法使用。

函数式是最优解

通过分析可以看出,三个方法里的for循环是一模一样的,唯一的不同点就是这三个方法都有自己的调用逻辑:a()调用Procedure.test1(Integer)b()调用Procedure.test2(Integer)c()调用Procedure.test3(Integer)

那么我们就可以把这个调用逻辑作为一个方法的实现内容进行传递,交给println()方法,让其在适当的地方调用。

这个时候,函数式编程就像魔法一样,把这个类的代码结构优化到淋漓尽致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Share {
public static void a() {
//简写
println(Procedure::test1);
}
public static void b() {
//简写
println(Procedure::test2);
}
public static void c() {
//为了讲解,此处使用完整写法
println((t)->{
return Procedure.test3(t);
});
}
public static void println(Function<Integer, Integer> func) {
for (int i = 0; i <= 3; i++) {
Integer value = func.apply(i);
System.out.println("代码第" + i + "次执行结果:" + value);
}
}
public static void main(String[] args) {
a();
}
}

Java 内置的函数式接口

Java 是一种强类型语言,所以在函数式编程这一特性也不例外,它不能像 JS 那样可以将任何形式(这里指参数、返回值)的函数进行传递,而是必须在函数接受方显式的规定该函数的数据类型等细节。

Java 利用泛型内置了一些可以灵活使用的函数式接口,你可以通过指定这些接口的泛型达到定制化的目的,来满足一般的开发需求。

接口名称关键字整理

由于内置的函数式接口较多,这里就不再一一列举,可以点击访问菜鸟教程网站进行查阅

不过我发现这些接口的名称和他们的场景用途存在一定的规律,特地整理一份关键字的对照表。

关键字 返回值 参数 执行
Consumer 无返回结果 有参数 accept方法
Supplier 有返回结果 无参数 get方法
Function 有返回结果 有参数 apply方法
Predicate 有返回结果,且结果为布尔值 有参数 test方法
Operator 有返回结果,且参数和返回结果数据类型相同 有参数,且参数和返回结果数据类型相同 apply方法

如何自定义函数式接口

当 Java 内置的函数式接口不能满足开发需求时(例如接口的参数数量很多),你可以自己定义一个函数式接口来使用。

当然,出于个人喜好、项目管理等原因,你也可以在项目中完全使用自定义的函数式接口,放弃所有的内置函数式接口。

下面就是一个标准的函数式接口,看起来与普通的接口定义没有什么不同。

1
2
3
4
5
6
7
package com.zyan.test;

@FunctionalInterface
public interface TestInterFace {

Integer test(String a, String b, String c, String d);
}

此处需要注意的是,经过实践,@FunctionalInterface注解并不是必须的,出于习惯的原因,我都会加上该注解。

自定义函数式接口需要满足:有且只有一个未实现的方法。