开发者社区> xcbeyond> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

JDK1.8新特性(六):Stream的终极操作,轻松解决集合分组、汇总等复杂操作

简介: 本文将讲述关于Stream的终极操作,让你轻松解决集合的分组、汇总等操作,让其他同事对你刮目相看。
+关注继续查看

上一篇JDK1.8新特性(五):Stream,集合操作利器,让你好用到飞起来,主要讲解了关于Stream的基本操作,可以轻松摆脱 “遍历、再遍历、再运算” 等复杂操作,但Stream远远不止这些。本文将讲述关于Stream的终极操作,让你轻松解决集合的分组、汇总等操作,让其他同事对你刮目相看。


一、Collectors

java.util.stream.Collectors,是从JDK1.8开始新引入的一个类。从源码的类注释上,我们可以知道:Collectors实现了各种有用归约的操作,例如类型归类到新集合、根据不同标准汇总元素等。透过示例,能让我们眼前一亮,短短的一行代码却能处理如此强大、复杂的功能:汇总、拼接、累加计算、分组等


切记,不要用错哦,是java.util.stream.Collectors,不是java.util.Collections


/**
 * 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分别为truefalseMap中,从而我们能够得到符合和不符合的分组新集合。
  • 最值:按照某个属性查找最大、最小元素。
  • 累加、汇总:用来完成累加计算、数据汇总(总数、总和、最小值、最大值、平均值)。
  • 连接:将元素以某种规则连接起来。
  • ……


二、实战演练

1. 类型归类

将集合中元素按照类型、条件过滤等归类,存放到指定类型的新集合,ListMapSetCollection或者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));


如果我们不想返回MapvalueList怎么办?实际上可以按照下面的方式分组:

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分别为truefalseMap中,从而我们能够得到符合和不符合的分组新集合。


比如,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的使用过程中,你还遇到哪些好用、好玩的终极操作呢?


版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
掘金Markdown编辑器中的图片怎么居中?
掘金Markdown编辑器中的图片怎么居中?
46 0
html+css实战64-综合案例3-图片完成
html+css实战64-综合案例3-图片完成
8 0
30款非常漂亮的免费矢量图片素材
  设计师经常需要去网上搜罗一些矢量图片素材用于设计任务,网上的各种资源琳琅满目,要找到合适的素材可能需要花费大量的时间。今天这篇文章向设计师朋友分享30款非常漂亮的免费矢量图片素材。这些高质量的矢量素材既能为设计师节省时间,又能有很不错的效果。
829 0
Windows Embedded 7 Standard 裁剪过程图解教程
体验新一代WinCE,参与调查,赢大奖http://www.msembed.com/newsletter/201102/Compact7.
828 0
+关注
xcbeyond
专注于技术输出、分享。
32
文章
1
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载