Java Stream中reduce方法的使用
三种重载的方法
-
第一种
Optional<T> reduce(BinaryOperator<T> accumulator);
方法接受一个BinaryOperator类型的lambada表达式, 常规应用方法如下:
List<Integer> list = Arrays.asList(1,2,3,4); int result = list.stream().reduce((a,b) -> a+b).get(); System.out.println(result);
代码实现了对list 中的元素累加。lambada表达式的a参数是表达式的执行结果的缓存,也就是表达式这一次的执行结果会被作为下一次执行的参数,而第二个参数b则是依次为stream中每个元素。如果表达式是第一次被执行,a则是stream中的第一个元素。
-
第二种
T reduce(T identity, BinaryOperator<T> accumulator);
方法接受一个identity和BinaryOperator类型的lambada表达式, 与第一个签名的实现的唯一区别是它首次执行时表达式第一次参数并不是stream的第一个元素,而是通过签名的第一个参数identity来指定。我们来通过这个签名对之前的求和代码进行改进。
List<Integer> list = Arrays.asList(1,2,3,4); int result = list.stream().reduce(1, (a,b) -> a+b); System.out.println(result);
其实这两种实现几乎差别,第一种比第一种仅仅多了一个字定义初始值罢了。 此外,因为存在stream为空的情况,所以第一种实现并不直接方法计算的结果,而是将计算结果用Optional来包装,我们可以通过它的get方法获得一个Integer类型的结果,而Integer允许null。第二种实现因为允许指定初始值,因此即使stream为空,也不会出现返回结果为null的情况,当stream为空,reduce为直接把初始值返回。
-
第三种
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
第三种签名的用法相较前两种稍显复杂,犹豫前两种实现有一个缺陷,它们的计算结果必须和stream中的元素类型相同,如上面的代码示例,stream中的类型为int,那么计算结果也必须为int,这导致了灵活性的不足,甚至无法完成某些任务, 比入我们咬对一个一系列int值求和,但是求和的结果用一个int类型已经放不下,必须升级为long类型,此实第三签名就能发挥价值了,它不将执行结果与stream中元素的类型绑死。
List<Integer> list = Arrays.asList(Integer.MAX_VALUE,Integer.MAX_VALUE); long result = list .stream().reduce(0L,(a,b) -> a + b, (a,b)-> null); System.out.println(result);
注意该方法还有第三个参数,一个BinaryOperator类型的参数,此参数是用来处理多线程中每个线程的结果如何合并的问题。举例将执行结果简单相加:
List<Integer> list = Arrays.asList(1,2,3,4); int result = list.stream().parallel().reduce(1, (a,b) -> a+b, (a, b) -> a+b); System.out.println(result);
此时的执行结果: 居然是14,而不是11,说明第三个参数中,将结果简单相加是错误的。前两种方法如果使用parallel执行,那么结果也会是错误的。 原因是什么呢? 推想一下一下,parallel开启了四个线程进行计算分别是
Thread-1: 1+1=2 Thread-2: 1+2=3 Thread-3: 1+3=4 Thread-4: 1+4=5 //处理计算结果简单相加 2+3+4+5=14
看到这里你应该明白出错的原因了,identity(值为1)属性被多加了三次,要解决这个问题也很简单,在两两相加合并结果的时候减去一个identity(值为1)即可:
List<Integer> list = Arrays.asList(1,2,3,4); int result = list.stream().parallel().reduce(1, (a,b) -> a+b, (a, b) -> a+b-1); System.out.println(result);
运行结果: 可以推想此时的运行流程大致为:
Thread-1: 1+1=2 Thread-2: 1+2=3 Thread-3: 1+3=4 Thread-4: 1+4=5 //处理计算结果两两合并时减1 2+3-1=4 4+4-1=7 7+5-1=11
计算结果为11,正确
