JAVA8 stream中三个参数的reduce方法对List进行分组统计操作
背景
平时在编写前端代码时,习惯使用lodash来编写‘野生'的JavaScript;
lodash提供来一套完整的API对JS对象(Array,Object,Collection等)进行操作,这其中就包括_.groupBy和_.reduce,即分组和'聚合'(reduce不知道该怎么翻译合适)。
使用这些‘野生'的API能够极大的提高我本人编写JS代码的效率。而JAVA8开始支持stream和lambda表达式,这些和lodash的API有很多类似的功能。因此我在熟悉lodash的前提下尝试使用JAVA8的新特性减少冗余代码的编写。
需求
在开发后端某功能接口的过程中,需要对一个从数据库中取出的数据List
JAVA8reduceAPI
API个人理解
Ureduce(Uu,BiFunctionaccumulator,BinaryOperatorcombiner)#第一个参数返回实例u,传递你要返回的U类型对象的初始化实例u#第二个参数累加器accumulator,可以使用二元ℷ表达式(即二元lambda表达式),声明你在u上累加你的数据来源t的逻辑#例如(u,t)->u.sum(t),此时lambda表达式的行参列表是返回实例u和遍历的集合元素t,函数体是在u上累加t#第三个参数组合器combiner,同样是二元ℷ表达式,(u,t)->u#lambda表达式行参列表同样是(u,t),函数体返回的类型则要和第一个参数的类型保持一致
伪代码
#1.声明一个返回结果U #2.对List进行遍历,在U和每个T实例上应用一个累加器进行累加操作 #3.返回最终结果U Uresult=identity; for(Telement:thisstream) result=accumulator.apply(result,element) returnresult;
数据准备
varsource= [ {"name":"A","type":"san","typeValue":1.0,"count":2}, {"name":"A","type":"nas","typeValue":13.0,"count":1}, {"name":"B","type":"san","typeValue":112.0,"count":3}, {"name":"C","type":"san","typeValue":43.0,"count":5}, {"name":"B","type":"nas","typeValue":77.0,"count":7} ]; vartarget= [ { "name":"A", "count":3, "totalTypeValue":14.0, "bazList":[ { "type":"san", "typeValue":1.0 }, { "type":"nas" "typeValue":13.0 } ] }, { "name":"B", "count":10, "totalTypeValue":189.0, "bazList":[ { "type":"san", "typeValue":112.0 },{ "type":"nas" "typeValue":77.0 } ] }, { "name":"C", "count":5, "totalTypeValue":43.0, "bazList":[ { "type":"san", "typeValue":43.0 } ] } ];
Code
讲了那么多废话,这个才是最直接的
代码执行大意
对List
ReduceTest.java
importcom.google.common.collect.Lists; importBar; importFoo; importjava.util.List; importjava.util.stream.Collectors; publicclassReduceTest{ publicstaticvoidmain(String[]args)throwsException{ ListfooList=Lists.newArrayList( newFoo("A","san",1.0,2), newFoo("A","nas",13.0,1), newFoo("B","san",112.0,3), newFoo("C","san",43.0,5), newFoo("B","nas",77.0,7) ); List barList=Lists.newArrayList(); fooList .stream() .collect(Collectors.groupingBy(Foo::getName,Collectors.toList())) .forEach((name,fooListByName)->{ Barbar=newBar(); bar=fooListByName .stream() .reduce(bar,(u,t)->u.sum(t),(u,t)->u); System.out.println(bar.toString()); barList.add(bar); }); } /* 输出结果 name:A count:3 totalTypeValue:14.0 bazList: type:san typeValue:1.0 type:nas typeValue:13.0 name:B count:10 totalTypeValue:189.0 bazList: type:san typeValue:112.0 type:nas typeValue:77.0 name:C count:5 totalTypeValue:43.0 bazList: type:san typeValue:43.0 */ }
Foo.java
publicclassFoo{ privateStringname; privateStringtype; privateDoubletypeValue; privateIntegercount; publicFoo(Stringname,Stringtype,DoubletypeValue,Integercount){ this.name=name; this.type=type; this.typeValue=typeValue; this.count=count; } publicStringgetName(){ returnname; } publicvoidsetName(Stringname){ this.name=name; } publicStringgetType(){ returntype; } publicvoidsetType(Stringtype){ this.type=type; } publicDoublegetTypeValue(){ returntypeValue; } publicvoidsetTypeValue(DoubletypeValue){ this.typeValue=typeValue; } publicIntegergetCount(){ returncount; } publicvoidsetCount(Integercount){ this.count=count; } }
Bar.java
importcom.google.common.collect.Lists; importjava.util.List; publicclassBar{ privateStringname; privateIntegercount; privateDoubletotalTypeValue; privateListbazList; publicBar(){ this.name=null; this.count=0; this.totalTypeValue=0.0; this.bazList=Lists.newArrayList(); } publicBarsum(Foofoo){ if(name==null){ this.name=foo.getName(); } this.count+=foo.getCount(); this.totalTypeValue+=foo.getTypeValue(); this.bazList.add(newBaz(foo.getType(),foo.getTypeValue())); returnthis; } publicStringgetName(){ returnname; } publicvoidsetName(Stringname){ this.name=name; } publicIntegergetCount(){ returncount; } publicvoidsetCount(Integercount){ this.count=count; } @Override publicStringtoString(){ StringBuildersb=newStringBuilder(); sb.append("name:").append(this.name).append(System.lineSeparator()); sb.append("count:").append(this.count).append(System.lineSeparator()); sb.append("totalTypeValue:").append(this.totalTypeValue).append(System.lineSeparator()); sb.append("bazList:").append(System.lineSeparator()); this.bazList.forEach(baz->{ sb.append("\t").append("type:").append(baz.getType()).append(System.lineSeparator()); sb.append("\t").append("typeValue:").append(baz.getTypeValue()).append(System.lineSeparator()); }); returnsb.toString(); } }
Baz.java
publicclassBaz{ privateStringtype; privateDoubletypeValue; publicBaz(Stringtype,DoubletypeValue){ this.type=type; this.typeValue=typeValue; } publicStringgetType(){ returntype; } publicvoidsetType(Stringtype){ this.type=type; } publicDoublegetTypeValue(){ returntypeValue; } publicvoidsetTypeValue(DoubletypeValue){ this.typeValue=typeValue; } }
PS
等下次有空补上不使用stream().reduce实现同样操作的比较繁琐的代码,啦啦啦啦啦~~~
补充知识:Java8collect、reduce方法聚合操作详解
Stream的基本概念
Stream和集合的区别:
1.Stream不会自己存储元素。元素储存在底层集合或者根据需要产生。
2.Stream操作符不会改变源对象。相反,它会返回一个持有结果的新的Stream。
3.Stream操作可能是延迟执行的,这意味着它们会等到需要结果的时候才执行。
Stream操作的基本过程,可以归结为3个部分:
创建一个Stream。
在一个或者多个操作中,将指定的Stream转换为另一个Stream的中间操作。
通过终止(terminal)方法来产生一个结果。该操作会强制它之前的延时操作立即执行,这之后该Stream就不能再被使用了。
中间操作都是filter()、distinct()、sorted()、map()、flatMap()等,其一般是对数据集的整理(过滤、排序、匹配、抽取等)。
终止方法往往是完成对数据集中数据的处理,如forEach(),还有allMatch()、anyMatch()、findAny()、findFirst(),数值计算类的方法有sum、max、min、average等等。终止方法也可以是对集合的处理,如reduce()、collect()等等。
reduce()方法的处理方式一般是每次都产生新的数据集,而collect()方法是在原数据集的基础上进行更新,过程中不产生新的数据集。
Listnums=Arrays.asList(1,3,null,8,7,8,13,10);
nums.stream().filter(num->num!=null).distinct().forEach(System.out::println);
上面代码实现为过滤null值并去重,遍历结果,实现简洁明了。使用传统方法就相对繁琐的多。另外其中forEach即为终止操作方法,如果无该方法上面代码就没有任何操作。
filter、map、forEach、findAny等方法的使用都比较简单,这里省略。
下面介绍强大的聚合操作,其主要分为两种:
可变聚合:把输入的元素们累积到一个可变的容器中,比如Collection或者StringBuilder;
其他聚合:除去可变聚合,剩下的,一般都不是通过反复修改某个可变对象,而是通过把前一次的汇聚结果当成下一次的入参,反复如此。比如reduce,count,allMatch;
聚合操作reduce
Stream.reduce,返回单个的结果值,并且reduce操作每处理一个元素总是创建一个新值。常用的方法有average,sum,min,max,count,使用reduce方法都可实现。
这里主要介绍reduce方法:
Treduce(Tidentity,BinaryOperatoraccumulator)
identity:它允许用户提供一个循环计算的初始值。accumulator:计算的累加器,其方法签名为apply(Tt,Uu),在该reduce方法中第一个参数t为上次函数计算的返回值,第二个参数u为Stream中的元素,这个函数把这两个值计算apply,得到的和会被赋值给下次执行这个方法的第一个参数。有点绕看代码:
intvalue=Stream.of(1,2,3,4).reduce(100,(sum,item)->sum+item); Assert.assertSame(value,110); /*或者使用方法引用*/ value=Stream.of(1,2,3,4).reduce(100,Integer::sum);
这个例子中100即为计算初始值,每次相加计算值都会传递到下一次计算的第一个参数。
reduce还有其它两个重载方法:
Optionalreduce(BinaryOperatoraccumulator):与上面定义基本一样,无计算初始值,所以他返回的是一个Optional。
Ureduce(Uidentity,BiFunctionaccumulator,BinaryOperatorcombiner):与前面两个参数的reduce方法几乎一致,你只要注意到BinaryOperator其实实现了BiFunction和BinaryOperator两个接口。
收集结果collect
当你处理完流时,通常只是想查看一下结果,而不是将他们聚合为一个值。先看collect的基础方法,它接受三个参数:
Rcollect(Suppliersupplier,BiConsumeraccumulator,BiConsumercombiner)
supplier:一个能创造目标类型实例的方法。accumulator:一个将当元素添加到目标中的方法。combiner:一个将中间状态的多个结果整合到一起的方法(并发的时候会用到)。接着看代码:
Streamstream=Stream.of(1,2,3,4).filter(p->p>2); Listresult=stream.collect(()->newArrayList<>(),(list,item)->list.add(item),(one,two)->one.addAll(two)); /*或者使用方法引用*/ result=stream.collect(ArrayList::new,List::add,List::addAll);
这个例子即为过滤大于2的元素,将剩余结果收集到一个新的list中。
第一个方法生成一个新的ArrayList;
第二个方法中第一个参数是前面生成的ArrayList对象,第二个参数是stream中包含的元素,方法体就是把stream中的元素加入ArrayList对象中。第二个方法被反复调用直到原stream的元素被消费完毕;
第三个方法也是接受两个参数,这两个都是ArrayList类型的,方法体就是把第二个ArrayList全部加入到第一个中;
代码有点繁琐,或者使用collect的另一个重载方法:
Rcollect(Collectorcollector)
注意到Collector其实是上面supplier、accumulator、combiner的聚合体。那么上面代码就变成:
Listlist=Stream.of(1,2,3,4).filter(p->p>2).collect(Collectors.toList());
将结果收集到map中
先定义如下Person对象
classPerson{ publicStringname; publicintage; Person(Stringname,intage){ this.name=name; this.age=age; } @Override publicStringtoString(){ returnString.format("Person{name='%s',age=%d}",name,age); } }
假设你有一个Stream对象,希望将其中元素收集到一个map中,这样就可以根据他的名称来查找对应年龄,例如:
Mapresult=people.collect(HashMap::new,(map,p)->map.put(p.name,p.age),Map::putAll);/*使用Collectors.toMap形式*/
Mapresult=people.collect(Collectors.toMap(p->p.name,p->p.age,(exsit,newv)->newv));
其中Collectors.toMap方法的第三个参数为键值重复处理策略,如果不传入第三个参数,当有相同的键时,会抛出一个IlleageStateException。
或者你想将Person分解为Map存储:
List
分组和分片
对具有相同特性的值进行分组是一个很常见的任务,Collectors提供了一个groupingBy方法,方法签名为:
Collector
groupingBy(Functionclassifier,Collectordownstream)
classifier:一个获取Stream元素中主键方法。downstream:一个操作对应分组后的结果的方法。
假如要根据年龄来分组:
Map
peopleByAge=people.filter(p->p.age>12).collect(Collectors.groupingBy(p->p.age,Collectors.toList()));
假如我想要根据年龄分组,年龄对应的键值List存储的为Person的姓名,怎么做呢:
Map
peopleByAge=people.collect(Collectors.groupingBy(p->p.age,Collectors.mapping((Personp)->p.name,Collectors.toList())));
mapping即为对各组进行投影操作,和Stream的map方法基本一致。
假如要根据姓名分组,获取每个姓名下人的年龄总和(好像需求有些坑爹):
MapsumAgeByName=people.collect(Collectors.groupingBy(p->p.name,Collectors.reducing(0,(Personp)->p.age,Integer::sum))); /*或者使用summingInt方法*/ sumAgeByName=people.collect(Collectors.groupingBy(p->p.name,Collectors.summingInt((Personp)->p.age)));
可以看到Java8的分组功能相当强大,当然你还可以完成更复杂的功能。另外Collectors中还存在一个类似groupingBy的方法:partitioningBy,它们的区别是partitioningBy为键值为Boolean类型的groupingBy,这种情况下它比groupingBy更有效率。
join和统计功能
话说Java8中新增了一个StringJoiner,Collectors的join功能和它基本一样。用于将流中字符串拼接并收集起来,使用很简单:
Stringnames=people.map(p->p.name).collect(Collectors.joining(","))
Collectors分别提供了求平均值averaging、总数couting、最小值minBy、最大值maxBy、求和suming等操作。但是假如你希望将流中结果聚合为一个总和、平均值、最大值、最小值,那么Collectors.summarizing(Int/Long/Double)就是为你准备的,它可以一次行获取前面的所有结果,其返回值为(Int/Long/Double)SummaryStatistics。
DoubleSummaryStatisticsdss=people.collect(Collectors.summarizingDouble((Personp)->p.age)); doubleaverage=dss.getAverage(); doublemax=dss.getMax(); doublemin=dss.getMin(); doublesum=dss.getSum(); doublecount=dss.getCount();
以上这篇JAVA8stream中三个参数的reduce方法对List进行分组统计操作就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。