从 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
注解并不是必须的,出于习惯的原因,我都会加上该注解。
自定义函数式接口需要满足:有且只有一个未实现的方法。