(全文阅读时间大概3分钟)JDK8通过java.util.function包提供了函数式编程(FP,functionalprogramming),Oracle此举把FP融入了Java这样一种命令式编程语言(imperativeprogramming)中。虽然目前Java中的FP不是纯粹的FP(pureFP),但它为Java注入了新的编程风格,新的编程思路,新的编程框架。
所谓命令式编程语言包括过程式编程语言(Procedure programming)和面向对象编程语言(Object oriented programming),它们关注如何解决问题,而FP关注如何描述问题,问题描述清楚后,问题也就解决了。使用命令式编程语言,程序的逻辑由一条条语句,一个个方法,对象以及它们之间的调用来完成,程序流程分支庞杂,测试困难,分支覆盖不全,很容易就导致程序崩溃,异常如果处理不当,也会埋藏隐患。
如果说软件正在“吞噬”着世界,那么函数则正在支配着软件。函数式编程正是函数支配软件的一个体现,它的基本思想利用函数来解决程序逻辑问题,和数学中的函数一样,相同的输入必定对应相同的输出,即函数是无状态的。其数学基础包括 演算(lambda calculus)和范畴论(category theory)等。
归纳起来,FP有以下特点:
1) 函数是“一等公民”(first class citizens)。OOP关注对象方法的调用,而FP关注函数之间的调用,因此习惯OOP的开发者需要改变思维来习惯FP的思想。
2) 状态不变(immutable state)。纯粹的函数都是无状态的,一系列的函数调用即transformation并不会改变初始对象的状态,只是程序运行的状态。
3) 支持高阶函数(higher-order functions):即一个或多个函数作为函数参数传入,或函数的返回结果是一个函数,满足其中一个条件即为高阶函数。
4) 无副作用(no side effects)。纯粹的函数都是无副作用的,即不依赖global变量,不会修改对象状态。
5) 支持函数组合(function composition):需要关注函数链条的执行顺序。
6) 惰性求值(Lazy evaluation):指一系列嵌套的函数的执行被延迟到其值需要的时刻。
下面看下JDK的具体实现。JDK8引入的FP接口主要放在了java.util.function包,我们选取有代表性的Function接口介绍。全文对OOP中的方法和FP中的函数不做过多区分,默认读者通过上述FP的特点能够领会。
- Function接口:
![6c89eb63b33f705552a157782184e3d5.png](https://i-blog.csdnimg.cn/blog_migrate/3921ffd205f06d64e0c3f58d9fc38458.jpeg)
- FuntionalInterface注解表明其是一个函数接口,事实上任意一个接口如果只有一个抽象方法,都可作为函数接口使用,不过覆盖Object类的public方法的接口除外。函数R apply(T t)方法表明Function接收一个T类型的入参,返回一个R类型的对象。可以通过以下方式声明一个Function实例:
- 方法一,通过lambda表达式:
![d09704e0bacb349b3f08f973a60e1f98.png](https://i-blog.csdnimg.cn/blog_migrate/2c8c3e0ed23afd363e4d4c2561da8b49.jpeg)
- 方法二:通过类引用(这里CLassName代表了multMethod所在的类名):
![ce077ed192223c165df8dee702e3b0db.png](https://i-blog.csdnimg.cn/blog_migrate/5e069bb979bcc1d151756c25b2c88163.jpeg)
- 方法三:通过匿名类返回:
![c3f350d07d3a7651cb189c0dd624704c.png](https://i-blog.csdnimg.cn/blog_migrate/9ea34f996f912b213c098cc72da6bcf4.jpeg)
- Function中的函数组合:Function接口中包括两个default方法,
![0032c3c11b5f162372889b989470af8c.png](https://i-blog.csdnimg.cn/blog_migrate/c804230a25af79a4ec41992ecab64388.jpeg)
- compose组合函数先执行before函数,再执行当前函数;相反andThen先执行当前函数,再执行after函数。例如
![33e0e0299211d59962ae73214fcd0c8d.png](https://i-blog.csdnimg.cn/blog_migrate/5078b0e6817935fa564e60205ffc6314.jpeg)
- 内层函数innerFun输出类型为Long,外层函数输入类型为Number,也就是说内层函数的输出类型必须为外层函数输入类型的子类,compose的方法签名中before函数的返回值范型为<? extends T>也说明这一点(范型中extends代表?(wildcard)继承自T,即?的上界(upper bounded)是T),before函数的入参<? super V>,V默认为Object类型(即?的下界(lower bounded)是V),因此入参可以是任意类型。参见范型相关介绍。
- Consumer接口代表只有一个入参,没有返回的函数。抽象方法:void accept(T t); 表明代码中任何只有一个入参,没有返回的方法都可作为Consumer函数。函数组合方法:
![25cf505cdfee56a056e75444ef5ba63c.png](https://i-blog.csdnimg.cn/blog_migrate/85ddcc3e71874e317af28da147ea4fd5.jpeg)
- 该方法表明Consumer函数链消费相同的入参,后续函数的入参类型为前序函数入参的父类,即函数链越往后,入参类型越抽象。例如:
![859f355ebd8fa35beee1e2805458b7a1.png](https://i-blog.csdnimg.cn/blog_migrate/11301b37055fa9f4edc10990d68f4977.jpeg)
- Supplier接口只有一个 T get()方法,该接口代表只有一个类型为T的返回值,没有入参,代码中任何只有一个返回值,没有入参的方法都可作为一个Supplier函数。
- UnaryOperator接口继承自Funtion接口,接口定义:public interface UnaryOperator<T>extends Function<T, T>,代表一元运算函数,即返回值与入参的类型一致,这个是与Funtion接口的区别。UnaryOperator与Funtion中都有一个静态identity方法,两者效果一样,返回值即为入参,所不同的就是UnaryOperator返回一个UnaryOperator,而Funtion中的identity返回一个Funtion。
- Predicate接口代表逻辑判断函数。抽象方法boolean test(Tt)说明根据入参t,判断其是否满足一定的逻辑条件,返回bool结果。三个default方法:
![1353442b93180fc8ee745b7a3c33d911.png](https://i-blog.csdnimg.cn/blog_migrate/2e31d6ce1c14eef9aab2af204e333c72.jpeg)
- 分别代表了逻辑与,逻辑非,逻辑或。isEqual用于判断两个对象是否相 等的Predicate函数。
![745fa5c8811580969a1d02b46ac373d2.png](https://i-blog.csdnimg.cn/blog_migrate/012331aa96ec571f0af6156d888db388.jpeg)
另外,java.util.function下Bi*接口代表了双元函数,即输入参数为两个;其他包含有具体类型的接口,如:Long*,ToDouble*,LongBinaryOperator等适用于特定的类型,在熟悉了上述Function,Condumer,UnaryOperator,Predicate等接口后,就非常容易理解和运用它们了。