java8集合Stream之reduce聚合函数

您所在的位置:网站首页 stream的map函数 java8集合Stream之reduce聚合函数

java8集合Stream之reduce聚合函数

2024-07-17 18:31:51| 来源: 网络整理| 查看: 265

说明

java8集合中Stream()相关函数都支持lambda表达式,reduce()就是其中之一, reduce是一种聚合操作,聚合的含义就是将多个值经过特定计算之后得到单个值, 常见的 count 、sum 、avg 、max 、min 等函数就是一种聚合操作。

本文使用reduce函数做求和计算来说明它的用法:

reduce有三个重载方法 1.一个参数的reduce Optional reduce(BinaryOperator accumulator);

参数: BinaryOperator accumulator , BinaryOperator 继承于 BiFunction, 这里实现 BiFunction.apply(param1,param2) 接口即可。支持lambda表达式,形如:(result,item)->{...} 。

返回值:返回Optional对象,由于结果存在空指针的情况(当集合为空时)因此需要使用Optional。

如下代码通过reduce 求整数集合中的元素之和:

import com.google.common.collect.Lists; import java.util.List; public class LambdaTest { public static void main(String[] args) { List list=Lists.newArrayList(1,2,3,4,5); //将数组进行累加求和 //由于返回的是 Optional ,因此需要get()取出值。 Integer total=list.stream().reduce((result,item)->result+item).get(); System.out.println(total); } } //结果为: 15

将累加的每一步打印,可以发现Lambda表达式中的两个参数(result,item)的含义: 第一个参数 result :初始值为集合中的第一个元素,后面为每次的累加计算结果 ; 第二个参数 item :遍历的集合中的每一个元素(从第二个元素开始,第一个被result使用了)。

List list=Lists.newArrayList(1,2,3,4,5); list.stream().reduce((result,item)->{ System.out.println("result="+result+", item="+item); return result+item; }); /* 结果如下: result=1, item=2 result=3, item=3 result=6, item=4 result=10, item=5 */ 2.两个参数的reduce T reduce(T identity, BinaryOperator accumulator);

参数1:T identity 为一个初始值(默认值) ,当集合为空时,就返回这个默认值,当集合不为空时,该值也会参与计算; 参数2:BinaryOperator accumulator 这个与一个参数的reduce相同。 返回值:并非 Optional,由于有默认值 identity ,因此计算结果不存在空指针的情况。

List list=Lists.newArrayList(1,2,3,4,5); Integer total=list.stream().reduce(0,(result,item)->result+item); System.out.println(total);//结果为:15 list=new ArrayList(); total=list.stream().reduce(0,(result,item)->result+item); System.out.println(total);//数组为空时,结果返回默认值0 3.三个参数的reduce U reduce(U identity, BiFunction accumulator,BinaryOperator combiner);

第一个参数和第二个参数的定义同上,第三个参数比较特殊,后面慢慢讲。

可以看到该方法有两个泛型 T 和 U : (1)泛型T是集合中元素的类型, (2)泛型U是计算之后返回结果的类型,U的类型由第一个参数 identity 决定。 也就是说,三个参数的reduce()可以返回与集合中的元素不同类型的值,方便我们对复杂对象做计算式和转换。 而一个参数和两个参数的reduce()只能返回与集合中的元素同类型的值。

现在我们在集合中存放 ScoreBean 对象,模拟学生分数统计:

static class ScoreBean { private String name; //学生姓名 private int score; //分数,需要汇总该字段 public ScoreBean(String name, int score) { this.name = name; this.score = score; } //get 和 set 方法省略 }

我们对 ScoreBean 中 score 字段汇总:(后面示例代码中的list定义省略,都用这个)

List list= Lists.newArrayList( new ScoreBean("张三",1) ,new ScoreBean("李四",2) ,new ScoreBean("王五",3) ,new ScoreBean("小明",4) ,new ScoreBean("小红",5)); Integer total=list.stream() .reduce( Integer.valueOf(0) /*初始值 identity*/ ,(integer,scoreBean)->integer+scoreBean.getScore() /*累加计算 accumulator*/ ,(integer1,integer2)->integer1+integer2 /*第三个参数 combiner*/ ); System.out.println(total);//结果:15

其实这个相当于:

Integer total=list.stream().mapToInt(ScoreBean::getScore).sum(); System.out.println(total);//结果也是:15

第三个参数 BinaryOperator combiner 是个什么鬼? 这个参数的lambda表达式我是这么写的:(integer1,integer2)->integer1+integer2) 现在我将其打印出来:

Integer total=list.stream() .reduce( Integer.valueOf(0) ,(integer,scoreBean)->integer+scoreBean.getScore() ,(integer1,integer2)->{ //这个println居然没有执行!!! System.out.println("integer1="+integer1+", integer2="+integer2); return integer1+integer2; } );

发现这个参数的lambda表达式根本就没有执行?! 我换了一种方式,换成 parallelStream ,然后把线程id打印出来:

//Integer total=list.stream() Integer total=list.parallelStream() .reduce( Integer.valueOf(0) ,(integer,scoreBean)->integer+scoreBean.getScore() ,(integer1,integer2)->{ //由于用的 parallelStream ,可发生并行计算,所以我增加线程id的打印: System.out.println("threadId="+Thread.currentThread().getId()+", integer1="+integer1+", integer2="+integer2); return integer1+integer2; } ); /*结果如下: threadId=13, integer1=1, integer2=2 threadId=1, integer1=4, integer2=5 threadId=1, integer1=3, integer2=9 threadId=1, integer1=3, integer2=12 */

把 stream 换成并行的 parallelStream, 可以看出,有两个线程在执行任务:线程13和线程1 , 每个线程会分配几个元素做计算, 如上面的线程13分配了元素1和2,线程1分配了3、4、5。 至于线程1为什么会有两个3,是由于线程13执行完后得到的结果为3(1+2),而这个3又会作为后续线程1的入参进行汇总计算。 可以多跑几次,每次执行的结果不一定相同,如果看不出来规律,可以尝试增加集合中的元素个数,数据量大更有利于并行计算发挥作用。

因此,第三个参数 BinaryOperator combiner 的作用为:汇总所有线程的计算结果得到最终结果。 并行计算会启动多个线程执行同一个计算任务,每个线程计算完后会有一个结果,最后要将这些结果汇总得到最终结果。

我们再来看一个有意思的结果,把第一个参数 identity 从0换成1:

//Integer total=list.stream() Integer total=list.parallelStream() .reduce( Integer.valueOf(1) ,(integer,scoreBean)->{ System.out.println("$ threadId="+Thread.currentThread().getId()+", integer="+integer+", scoreBean.getScore()="+scoreBean.getScore()); return integer+scoreBean.getScore(); } ,(integer1,integer2)->{ System.out.println("threadId="+Thread.currentThread().getId()+", integer1="+integer1+", integer2="+integer2); return integer1+integer2; } ); System.out.println("result="+total); /* 运行结果如下: $ threadId=12, integer=1, scoreBean.getScore()=2 $ threadId=1, integer=1, scoreBean.getScore()=3 $ threadId=14, integer=1, scoreBean.getScore()=5 $ threadId=13, integer=1, scoreBean.getScore()=1 $ threadId=15, integer=1, scoreBean.getScore()=4 threadId=13, integer1=2, integer2=3 threadId=15, integer1=5, integer2=6 threadId=15, integer1=4, integer2=11 threadId=15, integer1=5, integer2=15 result=20 */

预期结果应该是16(初始值1+原来的结果15),但实际结果为20,多加了4次1,猜测是多加了四次初始值, 从打印的结果可以发现: (1)并行计算时用了5个线程(线程id依次为:12, 1, 14, 13, 15),汇总合并时用了两个线程(线程id为13和15) (2)并行计算的每一个线程都用了初始值参与计算,因此多加了4次初始值。

总结: 使用 parallelStream 时,初始值 identity 应该设置一个不影响计算结果的值,比如本示例中设置为 0 就不会影响结果。 我觉得这个初始值 identity 有两个作用:确定泛型U的类型 和 避免空指针。 但是如果初始值本身就是一个复杂对象那该怎么办呢? 比如是初始值是一个数组,那么应该设定为一个空数组。如果是其他复杂对象那就得根据你reduce的具体含义来设定初始值了。

用表达式来解释就是初始值identity应该满足以下等式:

combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t) //combiner.apply(u1,u2) 接收两个相同类型U的参数 //accumulator.apply(u, t) 接收两个不同类型的参数U和T,U是返回值的类型,T是集合中元素的类型 //这个等式恒等,parallelStream计算时就不会产生错误结果


【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


    图片新闻

    实验室药品柜的特性有哪些
    实验室药品柜是实验室家具的重要组成部分之一,主要
    小学科学实验中有哪些教学
    计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
    实验室各种仪器原理动图讲
    1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
    高中化学常见仪器及实验装
    1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
    微生物操作主要设备和器具
    今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
    浅谈通风柜使用基本常识
     众所周知,通风柜功能中最主要的就是排气功能。在

    专题文章

      CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭