JDK1.8新特性(六):Stream的终极操作,轻松解决集合分组、汇总等复杂操作
上一篇JDK1.8新特性(五):Stream,集合操作利器,让你好用到飞起来,主要讲解了关于Stream的基本操作,可以轻松摆脱**"遍历、再遍历、再运算"**等复杂操作,但Stream远远不止这些。本文将讲述关于Stream的终极操作,让你轻松解决集合的分组、汇总等操作,让其他同事对你刮目相看。
一、Collectors
java.util.stream.Collectors
,是从JDK1.8开始新引入的一个类。从源码的类注释上,我们可以知道:<u>Collectors实现了各种有用归约的操作,例如类型归类到新集合、根据不同标准汇总元素等。</u>透过示例,能让我们眼前一亮,短短的一行代码却能处理如此强大、复杂的功能:汇总、拼接、累加计算、分组等。
<u>切记,不要用错哦,是java.util.stream.Collectors
,不是java.util.Collections
。</u>
/** * Implementations of {@link Collector} that implement various useful reduction * operations, such as accumulating elements into collections, summarizing * elements according to various criteria, etc. * * <p>The following are examples of using the predefined collectors to perform * common mutable reduction tasks: * * <pre>{@code * // Accumulate names into a List * List<String> list = people.stream().map(Person::getName).collect(Collectors.toList()); * * // Accumulate names into a TreeSet * Set<String> set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new)); * * // Convert elements to strings and concatenate them, separated by commas * String joined = things.stream() * .map(Object::toString) * .collect(Collectors.joining(", ")); * * // Compute sum of salaries of employee * int total = employees.stream() * .collect(Collectors.summingInt(Employee::getSalary))); * * // Group employees by department * Map<Department, List<Employee>> byDept * = employees.stream() * .collect(Collectors.groupingBy(Employee::getDepartment)); * * // Compute sum of salaries by department * Map<Department, Integer> totalByDept * = employees.stream() * .collect(Collectors.groupingBy(Employee::getDepartment, * Collectors.summingInt(Employee::getSalary))); * * // Partition students into passing and failing * Map<Boolean, List<Student>> passingFailing = * students.stream() * .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD)); * * }</pre> * * @since 1.8 */
换句话说,Collectors结合Stream将成为集合的终极操作,其中,包括:
- 类型归类:将集合中元素按照类型、条件过滤等归类,存放到指定类型的新集合。
- 分组:按照条件对元素进行分组,和SQL中
group by
的用法有异曲同工之妙。 - 分区:分组的特殊情况,实质是在做二分组,将符合条件、不符合条件的元素分组到两个
key
分别为true
和false
的Map
中,从而我们能够得到符合和不符合的分组新集合。 - 最值:按照某个属性查找最大、最小元素。
- 累加、汇总:用来完成累加计算、数据汇总(总数、总和、最小值、最大值、平均值)。
- 连接:将元素以某种规则连接起来。
- ……
二、实战演练
1. 类型归类
将集合中元素按照类型、条件过滤等归类,存放到指定类型的新集合,List
、Map
、Set
、Collection
或者ConcurrentMap
。涉及以下方法:
-
Collectors.toList()
-
Collectors.toMap()
-
Collectors.toSet()
-
Collectors.toCollection()
-
Collectors.toConcurrentMap()
一般都作为终止操作符cololect
的参数来使用,并伴随着流的结束。
常用于收集、筛选出集合(复杂集合)中的符合条件的数据,并存放于对应类型的新集合中,便于后续实际业务逻辑处理。
比如,将名字类型归类存在到List<String>
集合中:
List<String> list = allPeoples.stream().map(People::getName).collect(Collectors.toList());
2. 分组
按照条件对元素进行分组,和 SQL 中的 group by
用法有异曲同工之妙,通常也建议使用Java代码进行分组处理以减轻数据库SQL压力。
分组涉及以下方法:
-
Collectors.groupingBy(…):普通分组。
-
Collectors.groupingByConcurrent(…):线程安全的分组。
分组后,返回的是一个Map
集合,其中key
作为分组对象,value
作为对应分组结果。
比如,考虑到People集合中可能会存在同龄人,将集合按照年龄进行分组:
Map<Integer, List<People>> groupingByAge = allPeoples.stream().collect(Collectors.groupingBy(People::getAge));
如果我们不想返回Map
的value
为List
怎么办?实际上可以按照下面的方式分组:
Map<Integer, Set<People>> groupingByAge2 = allPeoples.stream().collect(Collectors.groupingBy(People::getAge, Collectors.toSet()));
考虑到同步安全问题时,怎么办?
采用线程安全的分组Collectors.groupingByConcurrent(…)
,于是:
Map<Integer, List<People>> groupingByAge3 = allPeoples.stream().collect(Collectors.groupingByConcurrent(People::getAge));
3. 分区
是分组的特殊情况,采用Collectors.partitioningBy(…)
方法来完成。
该方法实质是在做二分组,将符合条件、不符合条件的元素分组到两个key
分别为true
和false
的Map
中,从而我们能够得到符合和不符合的分组新集合。
比如,People集合中人名有中文名,也有英文名,将人名按照中、英文名进行分区:
Map<Boolean, List<People>> partitioningByName = allPeoples.stream().collect(Collectors.partitioningBy(people -> people.getName().matches("^[a-zA-Z]*"))); // 获取英文名集合 List<People> englishNames = partitioningByName.get(true); // 获取中文名集合 List<People> chineseNames = partitioningByName.get(false);
4. 最值
按照某个属性查找出最大或最小值元素,并且基于Comparator
接口来对其进行比较,返回一个Optional对象,并结合Optional.isPresent()
判断并取得最大或最小值。
涉及以下方法:
- Collectors.maxBy(…):最大值。
- Collectors.minBy(…):最小值。
比如,找到People集合中最大、最小年龄的人:
// 查找最大年龄的人 Optional<People> maxAgeOptional = allPeoples.stream().collect(Collectors.maxBy(Comparator.comparingInt(People::getAge))); People maxAgePeople = null; if (maxAgeOptional.isPresent()) { maxAgePeople = maxAgeOptional.get(); } // 查找最小年龄的人 Optional<People> minAgeOptional = allPeoples.stream().collect(Collectors.minBy(Comparator.comparingInt(People::getAge))); People minAgePeople = null; if (minAgeOptional.isPresent()) { minAgePeople = minAgeOptional.get(); }
5. 累加、汇总
用来完成累加计算、数据汇总(总数、总和、最小值、最大值、平均值)操作。
计算集合某个属性的总和,类似与SQL中的sum
函数。
涉及以下方法:
- Collectors.summingInt/Double/Long(…):按照某个属性求和。
- Collectors.summarizingInt/Double/Long(…):按照某个属性的数据进行汇总,得到其总数、总和、最小值、最大值、平均值。
比如,计算全体人员的薪资总和:
int salaryTotal = allPeoples.stream().collect(Collectors.summingInt(People::getSalary));
如果想要得到全体人员的薪资数据整体情况(包括总数、总和、最小值、最大值、平均值),怎么办呢?
难道分别要搞多个Stream流吗?
当然,没有这么麻烦,只需Collectors.summarizingInt
方法就可轻松搞定。
// 输出:IntSummaryStatistics{count=10, sum=45000, min=2000, average=4500.000000, max=7000} IntSummaryStatistics intSummaryStatistics = allPeoples.stream().collect(Collectors.summarizingInt(People::getSalary));
6. 连接
将元素以某种规则连接起来,得到一个连接字符串。
涉及以下方法:
- Collectors.joining():字符串直接连接。
- Collectors.joining(CharSequence delimiter):按照字符
delimiter
进行字符串连接。 - Collectors.joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix):按照前缀
prefix
,后缀suffix
,并以字符delimiter
进行字符串连接。
比如,将People集合中所有名字按照某种连接符进行字符串连接:
// 输出:xcbeyondNikiXiaoMing超哥小白小红LucyLily超级飞侠乐迪 String namesStr1 = allPeoples.stream().map(People::getName).collect(Collectors.joining()); // 输出:xcbeyond,Niki,XiaoMing,超哥,小白,小红,Lucy,Lily,超级飞侠,乐迪 String namesStr2 = allPeoples.stream().map(People::getName).collect(Collectors.joining(",")); // 输出:[xcbeyond,Niki,XiaoMing,超哥,小白,小红,Lucy,Lily,超级飞侠,乐迪] String namesStr3 = allPeoples.stream().map(People::getName).collect(Collectors.joining(",", "[", "]"));
三、总结
本文,只是针对JDK1.8java.util.stream.Collectors
中最好用的操作进行单独举例说明,不涉及嵌套、复合、叠加使用,实际业务场景下可能会涉及到多种操作的叠加、组合使用,需按需灵活使用即可。
如果你熟悉了上面这些操作,在面对复杂集合、处理复杂逻辑时,就会更加得心应手。尤其是分组、汇总,简直是太好用了。
在JDK1.8的使用过程中,你还遇到哪些好用、好玩的终极操作呢?

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
SpringCloud+SpringBoot b2b2c 分布式微服务商城电商之直播带货模块设计分析
近期我参与了公司电子商务平台中“海播”模块设计,其中包括直播、短视频带货两个模块,下面介绍一下直播带货模块: 业务流程如下 第一步:商家以“商家入驻”模式入驻电子商务平台后,对自己的店铺进行装修、发布商品等操作(具体会在后面商家详情里面进行讲解)。 第二步:商家对商品设置分销,商家在发布商品的时候,设置商品分销比例,如:一个杯子标价为120元,其中拿出20元进行分销设计,其中14元钱设置为一级分销,剩下的6元钱设置为二级分销。 第三步:如果用户在平台上进行直播带货,首先要开通直播服务,如:上传真实资料,购买主播服务(不是所有人都可以进行免费直播)。 第四步:主播提交资料后,后台进行严格审核后方可直播。 第五步: 成为主播后,主播可以打开主播端,可直接进行直播,如:创建直播间、分享直播间、创建预播、可以去平台选择自己要带的货(商品),数据统计等。 平台、技术、架构、设计思想 1. 涉及平台 平台管理、商家端(PC端、手机端)、买家平台(H5/公众号商城、小程序商城、APP端(IOS/Android)、微服务平台(业务服务)、系统服务(SpringCloud相关:Eureka、Config...
- 下一篇
Seppiko Chart 1.6 已经发布,Java 条形码库
Seppiko Chart 1.6 已经发布,Java 条形码库 此版本更新内容包括: Seppiko Chart是一个条形码微服务,已实现Code39、Code93、Code128、ITF、PDF417、DataMatrix、QRCode和解码API。基于Spring boot 2、ZXing、D_Project和Apache Batik。 如何使用:https://gitee.com/seppiko/chart/wikis 修复bug 升级 Seppiko Commons Utils 1.0.5.RELEASE 升级 Spring Boot 2.4.5 升级 Batik 1.14 详情查看:https://gitee.com/seppiko/chart/releases/1.6
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池