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