Java 8 Stream 的终极技巧——Collectors 功能与操作方法详解
本文实例讲述了Java8Stream的终极技巧——Collectors功能与操作方法。分享给大家供大家参考,具体如下:
1.前言
昨天在Collection移除元素操作相关的文章中提到了Collectors。相信很多同学对这个比较感兴趣,那我们今天就来研究一下Collectors。
2.Collectors的作用
Collectors是Java8加入的操作类,位于java.util.stream包下。它会根据不同的策略将元素收集归纳起来,比如最简单常用的是将元素装入Map、Set、List等可变容器中。特别对于Java8StreamApi来说非常有用。它提供了collect()方法来对Stream流进行终结操作派生出基于各种策略的结果集。我们就借助于Stream来熟悉一下Collectors吧。我们依然用昨天的例子:
Listservers=newArrayList<>(); servers.add("Felordcn"); servers.add("Tomcat"); servers.add("Jetty"); servers.add("Undertow"); servers.add("Resin");
3.Java8中Collectors的方法
Collectors提供了一系列的静态方法供我们使用,通常情况我们静态导入即可使用。接下来我们来看看都提供了哪些方法吧。
3.1类型归纳
这是一个系列,作用是将元素分别归纳进可变容器List、Map、Set、Collection或者ConcurrentMap。
Collectors.toList(); Collectors.toMap(); Collectors.toSet(); Collectors.toCollection(); Collectors.toConcurrentMap();
我们可以根据以上提供的API使用Stream的collect方法中的转换为熟悉的集合容器。非常简单这里不再演示。
3.2joining
将元素以某种规则连接起来。该方法有三种重载joining(CharSequencedelimiter)和joining(CharSequencedelimiter,CharSequenceprefix,CharSequencesuffix)
//输出FelordcnTomcatJettyUndertowResin servers.stream().collect(Collectors.joining()); //输出Felordcn,Tomcat,Jetty,Undertow,Resin servers.stream().collect(Collectors.joining(",")); //输出[Felordcn,Tomcat,Jetty,Undertow,Resin] servers.stream().collect(Collectors.joining(",","[","]"));
用的比较多的是读取HttpServletRequest中的body:
HttpServletRequest.getReader().lines().collect(Collectors.joining());
3.3collectingAndThen
该方法先执行了一个归纳操作,然后再对归纳的结果进行Function函数处理输出一个新的结果。
//比如我们将serversjoining然后转成大写,结果为:FELORDCN,TOMCAT,JETTY,UNDERTOW,RESIN servers.stream.collect(Collectors.collectingAndThen(Collectors.joining(","),String::toUpperCase));
3.4groupingBy
按照条件对元素进行分组,和SQL中的groupby用法有异曲同工之妙,通常也建议使用Java进行分组处理以减轻数据库压力。groupingBy也有三个重载方法
我们将servers按照长度进行分组:
//按照字符串长度进行分组符合条件的元素将组成一个List映射到以条件长度为key的Map>中 servers.stream.collect(Collectors.groupingBy(String::length))
如果我不想Map的value为List怎么办?上面的实现实际上调用了下面的方式:
//Map> servers.stream.collect(Collectors.groupingBy(String::length,Collectors.toSet()))
我要考虑同步安全问题怎么办?当然使用线程安全的同步容器啊,那前两种都用不成了吧!别急!我们来推断一下,其实第二种等同于下面的写法:
Supplier
这就非常好办了,我们提供一个同步Map不就行了,于是问题解决了:
Supplier
其实同步安全问题Collectors的另一个方法groupingByConcurrent给我们提供了解决方案。用法和groupingBy差不多。
3.5partitioningBy
partitioningBy我们在本文开头的提到的文章中已经见识过了,可以看作groupingBy的一个特例,基于断言(Predicate)策略分组。这里不再举例说明。
3.6counting
该方法归纳元素的的数量,非常简单,不再举例说明。
3.7maxBy/minBy
这两个方法分别提供了查找大小元素的操作,它们基于比较器接口Comparator来比较,返回的是一个Optional对象。我们来获取servers中最小长度的元素:
//Jetty Optionalmin=servers.stream.collect(Collectors.minBy(Comparator.comparingInt(String::length)));
这里其实Resin长度也是最小,这里遵循了"先入为主"的原则。当然Stream.min()可以很方便的获取最小长度的元素。maxBy同样的道理。
3.8summingInt/Double/Long
用来做累加计算。计算元素某个属性的总和,类似Mysql的sum函数,比如计算各个项目的盈利总和、计算本月的全部工资总和等等。我们这里就计算一下servers中字符串的长度之和(为了举例不考虑其它写法)。
//总长度32 servers.stream.collect(Collectors.summingInt(s->s.length()));
3.9summarizingInt/Double/Long
如果我们对3.6章节-3.8章节的操作结果都要怎么办?难不成我们搞5个Stream流吗?所以就有了summarizingInt、summarizingDouble、summarizingLong三个方法。
这三个方法通过对元素某个属性的提取,会返回对元素该属性的统计数据对象,分别对应IntSummaryStatistics、DoubleSummaryStatistics、LongSummaryStatistics。我们对servers中元素的长度进行统计:
DoubleSummaryStatisticsdoubleSummaryStatistics=servers.stream.collect(Collectors.summarizingDouble(String::length)); //{count=5,sum=32.000000,min=5.000000,average=6.400000,max=8.000000} System.out.println("doubleSummaryStatistics.toString()="+doubleSummaryStatistics.toString());
结果DoubleSummaryStatistics中包含了总数,总和,最小值,最大值,平均值五个指标。
3.10mapping
该方法是先对元素使用Function进行再加工操作,然后用另一个Collector归纳。比如我们先去掉servers中元素的首字母,然后将它们装入List。
//[elordcn,omcat,etty,ndertow,esin] servers.stream.collect(Collectors.mapping(s->s.substring(1),Collectors.toList()));
有点类似Stream先进行了map操作再进行collect:
servers.stream.map(s->s.substring(1)).collect(Collectors.toList());
3.11reducing
这个方法非常有用!但是如果要了解这个就必须了解其参数BinaryOperator
ComparatorbyHeight=Comparator.comparing(Person::getHeight); Map >tallestByCity=people.stream() .collect(Collectors.groupingBy(Person::getCity,Collectors.reducing(BinaryOperator.maxBy(byHeight))));
结合最开始给的例子你可以使用reducing找出最长的字符串试试。
上面这一层是根据Height属性找最高的Person,而且如果这个属性没有初始化值或者没有数据,很有可能拿不到结果所以给出的是Optional
比如我们给出高于2米的人作为identity。我们就可以统计每个城市不低于2米而且最高的那个人,当然如果该城市没有人高于2米则返回基准值identity:
ComparatorbyHeight=Comparator.comparing(Person::getHeight); Personidentity=newPerson(); identity.setHeight(2.); identity.setName("identity"); Map collect=persons.stream() .collect(Collectors.groupingBy(Person::getCity,Collectors.reducing(identity,BinaryOperator.maxBy(byHeight))));
这时候就确定一定会返回一个Person了,最起码会是基准值identity不再是Optional。
还有些情况,我们想在reducing的时候把Person的身高先四舍五入一下。这就需要我们做一个映射处理。定义一个Functionmapper来干这个活。那么上面的逻辑就可以变更为:
ComparatorbyHeight=Comparator.comparing(Person::getHeight); Personidentity=newPerson(); identity.setHeight(2.); identity.setName("identity"); //定义映射处理四舍五入 Function mapper=ps->{ Doubleheight=ps.getHeight(); BigDecimaldecimal=newBigDecimal(height); Doubled=decimal.setScale(1,BigDecimal.ROUND_HALF_UP).doubleValue(); ps.setHeight(d); returnps; }; Map collect=persons.stream() .collect(Collectors.groupingBy(Person::getCity,Collectors.reducing(identity,mapper,BinaryOperator.maxBy(byHeight))));
4.总结
今天我们对Java8中的Collectors进行了详细的讲解。如果你熟悉了Collectors操作Stream会更加得心应手。当然在Java8之后的Java9和Java12中Collectors都有新增的功能,后面有时间我们会继续进行讲解。敬请关注!
更多关于java算法相关内容感兴趣的读者可查看本站专题:《Java文件与目录操作技巧汇总》、《Java数据结构与算法教程》、《Java操作DOM节点技巧总结》和《Java缓存操作技巧汇总》
希望本文所述对大家java程序设计有所帮助。