一、Lambda与函数式编程
Lambda表达式是Java 8最流行最常用的功能特性。它将函数式编程概念引入Java,函数式编程的好处在于可以帮助我们节省大量的代码,非常方便易用,能够大幅度的提高我们的编码效率。首先,我们介绍lambda表达式是什么,并将传统的java代码写法转换为lambda表达式写法,以此了解lambda表达式都对传统代码做了哪些简化。
看如下两段代码,很清晰的说明了lambda表达式是表达接口函数的实现。在传统的开发中,我们习惯所有的行为定义代码都封装在方法体内,并通过对象引用执行,就像使用下面的代码:
1 | public class LambdaDemo { |
下面是将上述代码用Lambda实现,我们使用lambda表达式内联为函数调用参数将最初的9行代码下降到只有3行。
1 | public static void main(String[] args) { |
其中以下为lambda表达式:
1 | demo.printSomething(something, toPrint -> System.out.println(toPrint)); |
总结如下:
lambda表达式,表达的是接口函数
箭头左侧是函数的逗号分隔的形式参数列表
箭头右侧是函数体代码
lambda表达式表达的是接口函数,箭头左侧是函数参数,箭头右侧是函数体。函数的参数类型和返回值类型都可以省略,程序会根据接口定义的上下文自动确定数据类型。如果将java8 Stream API结合lambda表达式使用,编码效率将会大幅度提高!下面介绍Stream-API。
二、Stream-API
Java Stream函数式编程接口在Java 8中引入,与Lambda使用极大的方便了集合类数据处理的效率。Java Stream就是一个数据流经的管道,并且在管道中对数据进行操作,然后流入下一个管道。管道的功能包括:Filter(过滤)、Map(映射)、sort(排序)等,集合数据通过Java Stream管道处理之后,转化为另一组集合或数据输出。可以处理的对象有数组、集合类、文本文件。具体示意如下图所示:
我们可以先看一个 Stream API代替for循环的例子:
1 | List<String> nameStrs = Arrays.asList("zhangsan", "lisi", "wangwu","zhaoliu"); |
- 首先,我们使用Stream()函数,将一个List转换为管道流
- 调用filter函数过滤数组元素,过滤方法使用lambda表达式,以L开头的元素返回true被保留,其他的List元素被过滤掉
- 然后调用Map函数对管道流中每个元素进行处理,字母全部转换为大写
- 然后调用sort函数,对管道流中数据进行排序
- 最后调用collect函数toList,将管道流转换为List返回
最终的输出结果是:[LEMUR, LION]。大家可以想一想,上面的这些对数组进行遍历的代码,如果你用for循环来写,需要写多少行代码?
1)filter与谓语逻辑
filter的官方定义如下:
方法说明翻译为:返回一个流,该流包含与给定谓词匹配的该流的元素。由此得知:filter的起过滤作用。过滤的条件为谓词匹配。关于过滤作用我们可以看下面一个例子:
1 | //取出年龄大于70岁的男性用户 |
其中 e.getAge() > 70 、e.getGender().equals("Man")
可以在实体中定义为:
1 | public static Predicate<Employee> getOldEmp = one -> one.getAge()>70; |
上图就是将谓语逻辑的体现。谓语逻辑主要解决的问题是”做什么“、”是什么“或者是”怎么样“。通常情况下,filter函数中lambda表达式为一次性使用的谓词逻辑。如果我们的谓词逻辑需要被多处、多场景、多代码中使用,通常将它抽取出来单独定义到它所限定的主语实体中(如上)。其中谓语逻辑还提供了 and(并集)、or(交集)、negate(取反)语法。
2)map操作
Stream提供的map函数不仅可以处理数据、还可以转换数据(对象数据格式装换)的类型、其中当函数没有返回值或者参数就是返回值的时候可以使用peek函数、处理二位数组可以用flatMap。
3)状态与并行操作
Stream还提供了一些对状态的操作的方法,之前我们提到的ilter与map操作,还提供了sorted排序操作、distinct去重操作、limit和skip截取操作、Sorted排序。对于大数据量的情况想要提升运行速度,Stream提供了parallel()函数表示对管道中的元素进行并行处理。通常情况下,parallel()能够很好的利用CPU的多核处理器,达到更好的执行效率和性能,建议使用。但是有些特殊的情况下,parallel并不适。
4)Comparator接口与函数式接口
说到Comparator接口就要引出一个概念,函数式接口。所谓的函数式接口,实际上就是接口里面只能有一个抽象方法的接口。我们用到的Comparator接口就是一个典型的函数式接口,它只有一个抽象方法compare。
如图:函数式接口的特点为:
接口有且仅有一个抽象方法,如上图的抽象方法compare
允许定义静态非抽象方法
允许定义默认defalut非抽象方法(default方法也是java8才有的,见下文)
允许java.lang.Object中的public方法,如上图的方法equals。
FunctionInterface注解不是必须的,如果一个接口符合”函数式接口”定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错
函数式接口是专门为lambda表达式准备的,lambda表达式是只实现接口中唯一的抽象方法的匿名实现类。
JDK中的其他的函数式接口有:java.lang.Runnable、java.util.Comparator、java.util.concurrent.Callable、java.util.function包下的接口,如Consumer、Predicate、Supplier等
5)reduce与集合元素规约
Stream API为我们提供了Stream.reduce
用来实现集合元素的归约。reduce函数有三个参数:
- Identity标识:一个元素,它是归约操作的初始值,如果流为空,则为默认结果。
- Accumulator累加器:具有两个参数的函数:归约运算的部分结果和流的下一个元素。
- Combiner合并器(可选):当归约并行化时,或当累加器参数的类型与累加器实现的类型不匹配时,用于合并归约操作的部分结果的函数。
6)forEach和终端操作
Stream提供了我们对于集合的终端操作,如收集为Set、收集为List、收集到Array、收集为Map、分组收集groupingBy。
我们可以看下面两个例子:
1 | //将 Employee List 转换为 id-Employee 的map |
1 | //将用户按照性别分组 |
第一个例子举例了我们的常用操作list转map,第二个例子举例了groupingBy的使用。
7)Stream的性能
问:stream比for循环慢5倍,用这个是为了啥?
答:互联网是一个新闻泛滥的时代,三人成虎,以假乱真的事情时候发生。作为一个技术开发者,要自己去动手去做,不要人云亦云。
性能测试脱离业务场景就是片面的性能测试,只有你在生产环境下的运行结果才是真的。我们可以动手测试用Junit来测试 Stream的性能。
*测试用例一 *
测试用例:5亿个int随机数,求最小值
测试结论:
- 使用普通for循环,执行效率是Stream串行流的2倍。也就是说普通for循环性能更好。
- Stream并行流计算是普通for循环执行效率的4-5倍。
- Stream并行流计算 > 普通for循环 > Stream串行流计算
测试用例代码:
1 | mport com.github.houbb.junitperf.core.annotation.JunitPerfConfig; |
*测试用例二 *
测试用例:长度为10的1000000随机字符串,求最小值
测试结论(测试代码见后文):
普通for循环执行效率与Stream串行流不相上下
Stream并行流的执行效率远高于普通for循环
Stream并行流计算 > 普通for循环 = Stream串行流计算
测试代码:
1 | import com.github.houbb.junitperf.core.annotation.JunitPerfConfig; |
测试用例三
测试用例:10个用户,每人200个订单。按用户统计订单的总价。
测试结论(测试代码见后文):
Stream并行流的执行效率远高于普通for循环
Stream串行流的执行效率大于等于普通for循环
Stream并行流计算 > Stream串行流计算 >= 普通for循环
测试代码:
1 | import com.github.houbb.junitperf.core.annotation.JunitPerfConfig; |
结论:在大多数的核心业务场景下及常用数据结构下,Stream的执行效率比for循环更高
- Stream并行流计算 >> 普通for循环 ~= Stream串行流计算
- 数据容量越大,Stream流的执行效率越高。
- Stream并行流计算通常能够比较好的利用CPU的多核优势。CPU核心越多,Stream并行流计算效率越高。