Java 8 Stream 的终极技巧——Collectors 操作
1. 前言
昨天在 Collection 移除元素操作[1] 相关的文章中提到了 Collectors
。相信很多同学对这个比较感兴趣,那我们今天就来研究一下 Collectors
。
2. Collectors 的作用
Collectors
是 Java 8 加入的操作类,位于 java.util.stream
包下。它会根据不同的策略将元素收集归纳起来,比如最简单常用的是将元素装入Map
、Set
、List
等可变容器中。特别对于 Java 8 Stream Api[2] 来说非常有用。它提供了collect()
方法来对 Stream
流进行终结操作派生出基于各种策略的结果集。我们就借助于 Stream
来熟悉一下 Collectors
吧。我们依然用昨天的例子:
List<String> servers = new ArrayList<>(); servers.add("Felordcn"); servers.add("Tomcat"); servers.add("Jetty"); servers.add("Undertow"); servers.add("Resin");
3. Java 8 中 Collectors 的方法
Collectors
提供了一系列的静态方法供我们使用,通常情况我们静态导入即可使用。接下来我们来看看都提供了哪些方法吧。
3.1 类型归纳
这是一个系列,作用是将元素分别归纳进可变容器 List
、Map
、Set
、Collection
或者ConcurrentMap
。
Collectors.toList(); Collectors.toMap(); Collectors.toSet(); Collectors.toCollection(); Collectors.toConcurrentMap();
我们可以根据以上提供的 API 使用 Stream
的 collect
方法中的转换为熟悉的集合容器。非常简单这里不再演示。
3.2 joining
将元素以某种规则连接起来。该方法有三种重载 joining(CharSequence delimiter)
和 joining(CharSequence delimiter,CharSequence prefix,CharSequence suffix)
// 输出 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.3 collectingAndThen
该方法先执行了一个归纳操作,然后再对归纳的结果进行 Function
函数处理输出一个新的结果。
// 比如我们将servers joining 然后转成大写,结果为:FELORDCN,TOMCAT,JETTY,UNDERTOW,RESIN servers.stream.collect(Collectors.collectingAndThen(Collectors.joining(","), String::toUpperCase));
3.4 groupingBy
按照条件对元素进行分组,和 SQL 中的 group by
用法有异曲同工之妙,通常也建议使用 Java 进行分组处理以减轻数据库压力。groupingBy
也有三个重载方法 我们将 servers
按照长度进行分组:
// 按照字符串长度进行分组 符合条件的元素将组成一个 List 映射到以条件长度为key 的 Map<Integer, List<String>> 中servers.stream.collect(Collectors.groupingBy(String::length))
如果我不想 Map
的 value
为 List
怎么办?上面的实现实际上调用了下面的方式:
//Map<Integer, Set<String>> servers.stream.collect(Collectors.groupingBy(String::length, Collectors.toSet()))
我要考虑同步安全问题怎么办?当然使用线程安全的同步容器啊,那前两种都用不成了吧!别急!我们来推断一下,其实第二种等同于下面的写法:
Supplier<Map<Integer,Set<String>>> mapSupplier = HashMap::new; Map<Integer,Set<String>> collect = servers.stream.collect(Collectors.groupingBy(String::length, mapSupplier, Collectors.toSet()));
这就非常好办了,我们提供一个同步 Map
不就行了,于是问题解决了:
Supplier<Map<Integer, Set<String>>> mapSupplier = () -> Collections.synchronizedMap(new HashMap<>()); Map<Integer, Set<String>> collect = servers.stream.collect(Collectors.groupingBy(String::length, mapSupplier, Collectors.toSet()));
其实同步安全问题 Collectors
的另一个方法 groupingByConcurrent
给我们提供了解决方案。用法和 groupingBy
差不多。
3.5 partitioningBy
partitioningBy
我们在本文开头的提到的文章中已经见识过了,可以看作 groupingBy
的一个特例,基于断言(Predicate
)策略分组。这里不再举例说明。
3.6 counting
该方法归纳元素的的数量,非常简单,不再举例说明。
3.7 maxBy/minBy
这两个方法分别提供了查找大小元素的操作,它们基于比较器接口 Comparator
来比较 ,返回的是一个 Optional
对象。我们来获取 servers
中最小长度的元素:
// JettyOptional<String> min = servers.stream.collect(Collectors.minBy(Comparator.comparingInt(String::length)));
这里其实 Resin
长度也是最小,这里遵循了 "先入为主" 的原则 。当然 Stream.min()
可以很方便的获取最小长度的元素。maxBy
同样的道理。
3.8 summingInt/Double/Long
用来做累加计算。计算元素某个属性的总和,类似 Mysql 的 sum
函数,比如计算各个项目的盈利总和、计算本月的全部工资总和等等。我们这里就计算一下 servers
中字符串的长度之和 (为了举例不考虑其它写法)。
// 总长度 32 servers.stream.collect(Collectors.summingInt(s -> s.length()));
3.9 summarizingInt/Double/Long
如果我们对 3.6 章节-3.8 章节 的操作结果都要怎么办?难不成我们搞 5 个 Stream
流吗?所以就有了 summarizingInt
、summarizingDouble
、summarizingLong
三个方法。这三个方法通过对元素某个属性的提取,会返回对元素该属性的统计数据对象,分别对应 IntSummaryStatistics
、DoubleSummaryStatistics
、LongSummaryStatistics
。我们对 servers
中元素的长度进行统计:
DoubleSummaryStatistics doubleSummaryStatistics = 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.10 mapping
该方法是先对元素使用 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.11 reducing
这个方法非常有用!但是如果要了解这个就必须了解其参数 BinaryOperator<T>
。这是一个函数式接口,是给两个相同类型的量,返回一个跟这两个量相同类型的一个结果,伪表达式为 (T,T) -> T
。默认给了两个实现 maxBy
和 minBy
,根据比较器来比较大小并分别返回最大值或者最小值。当然你可以灵活定制。然后 reducing
就很好理解了,元素两两之间进行比较根据策略淘汰一个,随着轮次的进行元素个数就是 reduce
的。那这个有什么用处呢?Java 官方给了一个例子:统计每个城市最高的人。
Comparator<Person> byHeight = Comparator.comparing(Person::getHeight); Map<String, Optional<Person>> tallestByCity = people.stream() .collect(Collectors.groupingBy(Person::getCity, Collectors.reducing(BinaryOperator.maxBy(byHeight))));
结合最开始给的例子你可以使用 reducing
找出最长的字符串试试。
上面这一层是根据 Height
属性找最高的 Person
,而且如果这个属性没有初始化值或者没有数据,很有可能拿不到结果所以给出的是 Optional<Person>
。如果我们给出了 identity
作一个基准值,那么我们首先会跟这个基准值进行 BinaryOperator
操作。比如我们给出高于 2 米 的人作为 identity
。我们就可以统计每个城市不低于 2 米 而且最高的那个人,当然如果该城市没有人高于 2 米则返回基准值identity
:
Comparator<Person> byHeight = Comparator.comparing(Person::getHeight); Person identity= new Person(); identity.setHeight(2.); identity.setName("identity"); Map<String, Person> collect = persons.stream() .collect(Collectors.groupingBy(Person::getCity, Collectors.reducing(identity, BinaryOperator.maxBy(byHeight))));
这时候就确定一定会返回一个 Person
了,最起码会是基准值identity
不再是 Optional
。
还有些情况,我们想在 reducing
的时候把 Person
的身高先四舍五入一下。这就需要我们做一个映射处理。定义一个 Function<? super T, ? extends U> mapper
来干这个活。那么上面的逻辑就可以变更为:
Comparator<Person> byHeight = Comparator.comparing(Person::getHeight); Person identity = new Person(); identity.setHeight(2.); identity.setName("identity"); // 定义映射 处理 四舍五入 Function<Person, Person> mapper = ps -> { Double height = ps.getHeight();BigDecimal decimal = new BigDecimal(height); Double d = decimal.setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue(); ps.setHeight(d); return ps; }; Map<String, Person> collect = persons.stream() .collect(Collectors.groupingBy(Person::getCity, Collectors.reducing(identity, mapper, BinaryOperator.maxBy(byHeight))));
4. 总结
今天我们对 Java 8 中的 Collectors
进行了详细的讲解。如果你熟悉了 Collectors
操作 Stream 会更加得心应手。当然在 Java 8 之后的 Java 9 和 Java 12 中 Collectors
都有新增的功能, 后面有时间我们会继续进行讲解。敬请关注!
参考资料
[1]
Collection移除元素操作: https://www.felord.cn/remove-element-from-collection.html
[2]
Java 8 Stream Api: https://www.felord.cn/java8streamapi.html
Java 8 Stream 的终极技巧——Collectors 操作相关推荐
- Java 8 - Stream流骚操作解读
文章目录 分类 中间操作 终端操作 使用Stream流 筛选和切片 用谓词筛选 filter 筛选各异的元素 distinct 截短流 limit 跳过元素 skip 映射 对流中每一个元素应用函数 ...
- java 8 Stream中操作类型和peek的使用
文章目录 简介 中间操作和终止操作 peek 结论 java 8 Stream中操作类型和peek的使用 简介 java 8 stream作为流式操作有两种操作类型,中间操作和终止操作.这两种有什么区 ...
- [Java基础]Stream流的收集操作
代码如下: package CollectPack;import java.util.*; import java.util.stream.Collectors; import java.util.s ...
- Java 8 Stream中间操作(方法)示例
Java 8 Streams中间操作的完整指南. 所有内置Stream API中间操作(方法)的列表以及示例. 1.概述 在本教程中,我们将学习什么是 Java 8 Stream 中的中间操作 . 所 ...
- java stream 分组求和_Java stream List 求和、分组操作
Java stream List 求和.分组操作 前言 项目中经常会使用Stream操作一些集合数据,今天记录一下我经常使用的Stream操作 求和操作public static void main( ...
- java peek函数_Java 8 Stream Api 中的 peek 操作
1. 前言 我在 Java 8 Stream API中的 map 和flatMap 中讲述了Java8 Stream API中 map 操作和 flatMap 操作的区别.然后有小伙伴告诉我 peek ...
- Java 8 Stream流的常见操作
Stream流的使用 在 Java 8 中, 集合接口有两个方法来生成流: stream() − 为集合创建串行流.parallelStream() − 为集合创建并行流. 串行流 如果是数组的话,可 ...
- 吃透JAVA的Stream流操作,多年实践总结
在JAVA中,涉及到对数组.Collection等集合类中的元素进行操作的时候,通常会通过循环的方式进行逐个处理,或者使用Stream的方式进行处理. 例如,现在有这么一个需求: 从给定句子中返回单词 ...
- Java8的Stream中的Collectors操作求double类型和的坑
Java8的Stream中的Collectors操作求double类型和的坑 无敌踩坑王的我又双叒叕来了!!!!!! 例子: @Testpublic void testSumDouble() {dou ...
最新文章
- html5时间轴列表,HTML5 时间轴/日程安排表模板
- linux系统中定时器使用方法,Linux下实现定时器Timer的几种方法
- python按条件拆分列表元素_如何通过在python中拆分列表元素来创建列表?
- 排序算法之low B三人组
- 微信小程序开发错误:LoginError {type: ERR_WX_GET_USER_INFO}解决方法
- 基于Asp.Net Core MVC和AdminLTE的响应式管理后台之侧边栏处理
- Handler、Message的简单使用
- SQL多列查询最大值
- 【Java】Java垃圾回收机制
- try-catch lasterr
- 各种编程语言介绍,应用,性能等
- SPSS Statistical 软件的使用
- 卡方检验结果表格中出现非常多个卡方值和P值的原理和实现步骤
- ThinkPhP关联查询setEagerlyType遇到的问题
- 央视《每周质量报告》:揭秘假宽带真相
- 伯乐发卡网系统修复版源码
- Ubuntu断点续存之wget
- 求求你别再写上千行的类了,试试 IDEA 这些牛逼的重构技巧吧!
- SAP UI5 应用开发教程之八十一 - 采用 OPA5 进行 SAP UI5 集成测试(Integration Test)的一个例子试读版
- 亲身试验力荐:番茄工作法