Java8 Stream 中 Collectors 的操作
Collectors是java.util.stream包下的一个工具类,其中各个方法的返回值可以作为java.util.stream.Stream#collect的入参,实现对队列的各种操作,包括:分组、聚合等。
准备
定义Student类(用到了 lombok):
@Data
@AllArgsConstructor
public class Student {
private String id;
private String name;
private LocalDate birthday;
private int age;
private double score;
}
定义一组测试数据:
inal List<Student> students = Lists.newArrayList();
students.add(new Student("1", "张三", LocalDate.of(2009, Month.JANUARY, 1), 12, 12.123));
students.add(new Student("2", "李四", LocalDate.of(2010, Month.FEBRUARY, 2), 11, 22.123));
students.add(new Student("3", "王五", LocalDate.of(2011, Month.MARCH, 3), 10, 32.123));
数据统计
元素数量:counting
students.stream().collect(Collectors.counting())
平均值:averagingDouble、averagingInt、averagingLong
这几个方法是计算聚合元素的平均值,区别是输入参数需要是对应的类型。比如,求学生的分数平均值,因为分数是double类型,所以在不转类型的情况下,需要使用averagingDouble:
students.stream().collect(Collectors.averagingDouble(Student::getScore))
如果考虑转换精度,也是可以实现:
// 精度转换:
students.stream().collect(Collectors.averagingInt(s -> (int)s.getScore()))
students.stream().collect(Collectors.averagingLong(s -> (long)s.getScore()))
// 如果是求学生的平均年龄,因为年龄是int类型,就可以随意使用任何一个函数了:
students.stream().collect(Collectors.averagingInt(Student::getAge))
students.stream().collect(Collectors.averagingDouble(Student::getAge))
students.stream().collect(Collectors.averagingLong(Student::getAge))
// 注意:这三个方法的返回值都是Double类型。
和:summingDouble、summingInt、summingLong
这三个方法和上面的平均值方法类似,也是需要注意元素的类型,在需要类型转换时,需要强制转换:
students.stream().collect(Collectors.summingInt(s -> (int)s.getScore()))
// 66.369
students.stream().collect(Collectors.summingDouble(Student::getScore))
// 66
students.stream().collect(Collectors.summingLong(s -> (long)s.getScore()))
summingDouble返回的是Double类型、summingInt返回的是Integer类型,summingLong返回的是Long类型。
最大值/最小值元素:maxBy、minBy
这两个函数就是求聚合元素中指定比较器中的最大/最小元素。比如,求年龄最大/最小的Student对象:
students.stream().collect(Collectors.minBy(Comparator.comparing(Student::getAge)))
students.stream().collect(Collectors.maxBy(Comparator.comparing(Student::getAge)))
统计结果:summarizingDouble、summarizingInt、summarizingLong
students.stream().collect(Collectors.summarizingInt(s -> (int) s.getScore()))
students.stream().collect(Collectors.summarizingDouble(Student::getScore))
students.stream().collect(Collectors.summarizingLong(s -> (long) s.getScore()))
summarizingDouble返回DoubleSummaryStatistics类型,summarizingInt返回IntSummaryStatistics类型,summarizingLong返回LongSummaryStatistics类型。
聚合、分组
聚合元素:toList、toSet、toCollection
这几个函数是将聚合之后的元素,重新封装到队列中,然后返回。比如,得到所有Student的 ID 列表,只需要根据需要的结果类型使用不同的方法即可:
// List: [1, 2, 3]
final List<String> idList = students.stream().map(Student::getId).collect(Collectors.toList());
// Set: [1, 2, 3]
final Set<String> idSet = students.stream().map(Student::getId).collect(Collectors.toSet());
// TreeSet: [1, 2, 3]
final Collection<String> idTreeSet = students.stream().map(Student::getId).collect(Collectors.toCollection(TreeSet::new));
toList方法返回的是List子类,toSet返回的是Set子类,toCollection返回的是Collection子类。Collection的子类包括List、Set等众多子类,所以toCollection更加灵活。
聚合元素:toMap、toConcurrentMap
这两个方法的作用是将聚合元素,重新组装为Map结构,也就是 k-v 结构。两者用法一样,区别是toMap返回的是Map,toConcurrentMap返回ConcurrentMap,也就是说,toConcurrentMap返回的是线程安全的 Map 结构。
比如,我们需要聚合Student的 id:
final Map<String, Student> map11 = students.stream().collect(Collectors.toMap(Student::getId, Function.identity()));
但是,如果 id 有重复的,会抛出java.lang.IllegalStateException: Duplicate key异常,所以,为了保险起见,我们需要借助toMap另一个重载方法:
final Map<String, Student> map2 = students.stream().collect(Collectors.toMap(Student::getId, Function.identity(), (x, y) -> x));
可以看到,toMap有不同的重载方法,可以实现比较复杂的逻辑。比如,我们需要得到根据 id 分组的Student的姓名:
final Map<String, String> map3 = students.stream()
.collect(Collectors.toMap(Student::getId, Student::getName, (x, y) -> x));
比如,我们需要得到相同年龄得分最高的Student对象集合:
final Map<Integer, Student> map5 = students.stream()
.collect(Collectors.toMap(Student::getAge, Function.identity(), BinaryOperator.maxBy(Comparator.comparing(Student::getScore))));
分组:groupingBy、groupingByConcurrent
groupingBy与toMap都是将聚合元素进行分组,区别是,toMap结果是 1:1 的 k-v 结构,groupingBy的结果是 1:n 的 k-v 结构。
比如,我们对Student的年龄分组:
final Map<Integer, List<Student>> map1 = students.stream().collect(Collectors.groupingBy(Student::getAge));
final Map<Integer, Set<Student>> map12 = students.stream().collect(Collectors.groupingBy(Student::getAge, Collectors.toSet()));
既然groupingBy也是分组,是不是也能够实现与toMap类似的功能,比如,根据 id 分组的Student:
final Map<String, Student> map3 = students.stream()
.collect(Collectors.groupingBy(Student::getId, Collectors.collectingAndThen(Collectors.toList(), list -> list.get(0))));
// 对比toMap
final Map<String, Student> map2 = students.stream()
.collect(Collectors.toMap(Student::getId, Function.identity(), (x, y) -> x));
// 如果想要线程安全的Map,可以使用groupingByConcurrent。
分组:partitioningBy
partitioningBy与groupingBy的区别在于,partitioningBy借助Predicate断言,可以将集合元素分为true和false两部分。比如,按照年龄是否大于 11 分组:
final Map<Boolean, List<Student>> map6 = students.stream().collect(Collectors.partitioningBy(s -> s.getAge() > 11));
final Map<Boolean, Set<Student>> map7 = students.stream().collect(Collectors.partitioningBy(s -> s.getAge() > 11, Collectors.toSet()));
链接数据:joining
这个方法对String类型的元素进行聚合,拼接成一个字符串返回,作用与java.lang.String#join类似,提供了 3 个不同重载方法,可以实现不同的需要。比如:
Stream.of("java", "go", "sql").collect(Collectors.joining());
Stream.of("java", "go", "sql").collect(Collectors.joining(", "));
Stream.of("java", "go", "sql").collect(Collectors.joining(", ", "【", "】"));
// list对象里面的name用逗号拼接起来
String name = students.stream().map(Student::getName).collect(Collectors.joining(","));
操作链:collectingAndThen
这个方法在groupingBy的例子中出现过,它是先对集合进行一次聚合操作,然后通过Function定义的函数,对聚合后的结果再次处理。
比如groupingBy中的例子:
final Map<String, Student> map3 = students.stream()
.collect(Collectors.groupingBy(Student::getId, Collectors.collectingAndThen(Collectors.toList(), list -> list.get(0))));
显示将结果聚合成List列表,然后取列表的第 0 个元素返回,通过这种方式,实现 1:1 的 map 结构。
再来一个复杂一些的,找到聚合元素中年龄数据正确的Student列表:
students.stream()
.filter(s -> (LocalDate.now().getYear() - s.getBirthday().getYear()) != s.getAge())
.collect(Collectors.toList());
操作后聚合:mapping
mapping先通过Function函数处理数据,然后通过Collector方法聚合元素。比如,获取students的姓名列表:
students.stream()
.collect(Collectors.mapping(Student::getName, Collectors.toList()));
// 这种计算与java.util.stream.Stream#map方式类似:
students.stream().map(Student::getName).collect(Collectors.toList());
// 从这点上看,还是通过java.util.stream.Stream#map更清晰一些。
Java8 Stream 中 Collectors 的操作的更多相关文章
- java stream中Collectors的用法
目录 简介 Collectors.toList() Collectors.toSet() Collectors.toCollection() Collectors.toMap() Collectors ...
- Java8 Stream流API常用操作
Java版本现在已经发布到JDK13了,目前公司还是用的JDK8,还是有必要了解一些JDK8的新特性的,例如优雅判空的Optional类,操作集合的Stream流,函数式编程等等;这里就按操作例举一些 ...
- 【JDK8】Java8 Stream流API常用操作
Java版本现在已经发布到JDK13了,目前公司还是用的JDK8,还是有必要了解一些JDK8的新特性的,例如优雅判空的Optional类,操作集合的Stream流,函数式编程等等;这里就按操作例举一些 ...
- Java8 stream 中利用 groupingBy 进行多字段分组求和
Java8的groupingBy实现集合的分组,类似Mysql的group by分组功能,注意得到的是一个map 对集合按照单个属性分组 case1: List<String> items ...
- Java8 Stream 中 List 转 Map 问题总结
在使用 Java 的新特性 Collectors.toMap() 将 List 转换为 Map 时存在一些不容易发现的问题,这里总结一下备查. 空指针风险 java.lang.NullPointerE ...
- Stream中的Peek操作
1.引言 如果你试图对流操作中的流水线进行调试, 了解stream流水线每个操作之前和操作之后的中间值, 该如何去做? 首先我们看一个例子, 使用forEach将流操作的结果打印出来. 1 /** 2 ...
- Java8 stream学习
Java8初体验(二)Stream语法详解 Java 8 flatMap示例 第一个Stream Demo IDEA里面写Stream有个坑 虽然java文件中没错,但是但编译的时候还是报错了, In ...
- Stream流的这些操作,你得知道,对你工作有很大帮助
Stream流 Stream(流)是一个来自数据源的元素队列并支持聚合操作: 元素是特定类型的对象,形成一个队列. Java中的Stream并不会存储元素,而 是按需计算. 数据源 流的来源. 可以是 ...
- JAVA8之lambda表达式具体解释,及stream中的lambda使用
前言: 本人也是学习lambda不久,可能有些地方描写叙述有误,还请大家谅解及指正! lambda表达式具体解释 一.问题 1.什么是lambda表达式? 2.lambda表达式用来干什么的? 3.l ...
- JAVA8 Stream集合操作:中间方法和完结方法
StreamLambda为java8带了闭包,这一特性在集合操作中尤为重要:java8中支持对集合对象的stream进行函数式操作,此外,stream api也被集成进了collection api, ...
随机推荐
- 面试题-MySQL和Redis(更新版)
前言 MySQL和Redis部分的题目,是我根据Java Guide的面试突击版本V3.0再整理出来的,其中,我选择了一些比较重要的问题,并重新做出相应回答,并添加了一些比较重要的问题,希望对大家起到 ...
- C++宏定义中可变参数列表__VA_ARGS__ 及 QT 提供的宏 QT_OVERLOADED_MACRO
1. 基本用法 VA_ARGS 是 C/C++ 中的预定义宏,用于在宏定义中表示可变参数列表(Variadic Arguments),需与省略号 ... 配合使用.其核心作用是将宏调用中的可变参数原样 ...
- socket tcp断线重连
一.网上常用方法1.当Socket.Conneted == false时,调用如下函数进行判断 点击(此处)折叠或打开 /// <summary> /// 当socket.connecte ...
- vue-element-admin整合服务端代理api
1. 找到vue.config.js,在devServer中编辑如下 devServer: { port: port, open: true, overlay: { warnings: false, ...
- Cursor+playwright+mcp,好玩
Cursor+playwright+mcp能干嘛,我就不多说,本文只讲怎么用上 第一步,安装下载Cursor.node.js,至于什么python环境,playwright网上一堆教程,自己查 第二步 ...
- 【代码审计】Emlog存在SQL注入+XSS漏洞
1.源码简介 EMLOG 是一款轻量级开源博客和CMS建站系统,速度快.省资源.易上手,适合各种规模的站点搭建. 2.漏洞描述 EMLOG $keyword参数存在SQL注入漏洞. EMLOG art ...
- 2025.3.24 DP专题
题目按照主观难度增序排列 Luogu P1758 [NOI2009] 管道取珠 有上下两个长度分别为 \(n,m\) 的管道 \(a,b\),管道中有两种不同颜色的球用 \(A,B\) 表示.现在每次 ...
- Font Awesome文档使用手册
Font Awesome 字体为您提供可缩放矢量图标,它可以被定制大小.颜色.阴影以及任何可以用CSS的样式. 使用文档:https://fa4.uihtm.com/ Font Awesome 是一套 ...
- PC端自动化测试实战教程-5-pywinauto 操作PC端应用程序窗口 - 下篇(详细教程)
1.简介 上一篇宏哥主要讲解和介绍了如何获取PC端应用程序窗口信息和如何连接窗口对其进行操作的常用的几种方法.今天宏哥接着讲解和分享一下窗口的基本操作:最大化.最小化.恢复正常.关闭窗口.获取窗口状态 ...
- 使用php的openssl_encrypt和python的pycrypt进行跨语言的对称加密和解密问题
最近有一个业务需求,需要前端传递一个密码到后端,期间要对传递的密码通过进行对称加密,我们约定使用成熟的AES加密方法. 前端使用php,后端用python,但是发现前端兄弟加密后的字符串,在pytho ...