下面以一个常见的需求为例,分析Java 8的函数式编程与常规的面向对象式编程的不同之处。函数式编程和面向对象式编程最根本的不同之处在于,在面向对象的世界,函数功能不能独立于数据而存在,一个函数功能必须存在于一个包含数据的对象中,服务于特定的数据。也就是说,在面向对象时,对象是编程的最小单元,而一个对象将数据和作用于该数据的函数功能打包成一个整体,数据和函数是不可分割的一部分。此时函数只能为该数据服务,而该数据一般也只能使用定义于其上的函数。而这种函数与数据的结合其实是加深了数据和功能的耦合性。对于编程来说,紧耦合意味着不灵活和低泛化能力。
面向对象的编程中的诸多设计原则如面向接口编程原则,扩展大于继承的原则等都是为了降低对象间的耦合程度而努力。但面向对象编程是有原罪的,它从出生的那一刻起就将数据和函数绑定到一起。这一点决定了面向对象编程不可能做到彻底的解耦。而函数式编程的目的是将解偶进行到底。他要彻底的解耦。他要做的就是将数据和函数分开,函数是函数,数据是数据,没必要非得在一起。在函数式编程的世界,函数和数据是对等的位置,都作为最小的单元而出现。我的数据可以选择A函数来提供服务,也可以选择B函数来提供服务。我的函数不仅可以给C数据提供服务,也可以给D数据提供服务。我可以按我的需求来随意组合数据和函数,打破在面向对象式编程的世界中数据与函数绑定规则,拥抱更自由的世界。
在java上谈论函数式编程对有些人来说是一件可笑的事情,因为java从本质上说面向对象的,你不管如何给一只猴子化妆,他也变不成人。但是对有些不怎么挑剔的观众来说,只要化妆的技术足够高超,就可以把猴子当作人来看。他们(包括我)认为,通过对java现有技术的组合,可以实现对函数式编程的一些我们喜欢的特性的模拟,享受函数式编程的快乐和便利。基于此,java8推出Function包,可以一定程度上让我们感受函数式编程的快乐。关于Function包的使用,可以参看Java 8 Consumer、Supplier、Predicate、Function。
言归正传,进入今天的主题。我们通过一个常见的需求,来在讨论函数式编程和面向对象式编程的思想下,分别会有什么样的应对方法。
需求案例传入一个Long型的id List, 将其转换成加上特定前缀后缀的id key。如id为1对应的id key为 Number_01_cache_key
面向对象编程面向对象编程,就需要我们把数据(id List)和对数据的操作(convert函数的逻辑)放到同一个对象中去, 因此像下面这样的操作是典型的面向对象的编程过程:
DetailReader_Object_way.java
package ObejectProgramming; import java.util.ArrayList; import java.util.List; /** * @author longxingjian * Created on 2020-01-14 */ public class DetailReader_Object_way { private List<Long> ids; public DetailReader_Object_way(List<Long> ids){ this.ids = ids; } public List<String> convert(){ String format = "Number_%s_CACHE_KEY"; List<String> convertedCacheKeys = new ArrayList<>(); for(Long id:ids) convertedCacheKeys.add(String.format(format,id)); return convertedCacheKeys; } }如上将对id的转换逻辑写到convert操作中,这里面的转换逻辑只为本类的ids数据服务,且本类的ids数据只能使用convert操作中定义的转化逻辑。一旦我们的转换逻辑发生了变更,我们也必须修改业务代码。
下面是测试函数:
ObjectProgrammingTest.java
package ObejectProgramming; import java.util.ArrayList; import java.util.List; import org.junit.Test; /** * @author longxingjian * Created on 2020-01-14 */ public class ObjectProgrammingTest { @Test public void objectProgrammingTest(){ List<Long> ids = new ArrayList<>(); ids.add(1L); ids.add(2L); DetailReader_Object_way detailReader = new DetailReader_Object_way(ids); List<String> cacheKeys = detailReader.convert(); for (String s : cacheKeys) { System.out.println(s); } } }结果:
函数式编程的目标是解藕数据和函数服务。在java中对象是提供服务的最小单元,因此一个折衷的办法是,我们定义某一种服务,它只提供类似函数的单一服务,并且以对象传递的形式发送给另一个对象使用,好像我们把这个函数服务单独发送过去了一样,至于接收者怎么使用,那是他们的事情了。
对于函数式的服务, 在java中由于语言的要求必须以一个对象的形式提供,且我们要求它提供单一的服务,只做一件事,这个功能由java8的Funciton包为我们实现。Function对象的公开方法只有一个apply方法,这个对象作为这种服务的容器来使用。话不多说,来看代码: