刁钻的需求
有个领导叫老溜,我们叫他老六。
安排了这样一个需求,数据库返回来源不明的一串数字。
需要将这些数字两个分一组输出。
老六还画了个图,大概说了一下。
比如数据库返回的是:[0,1,2,3,4,5,6,7,8,9]
我们需要实现的效果是: [[0,1],[2,3],[4,5],[6,7],[8,9]]
意思明白了,下面就着手实现。
没有流收集器但有流
轻松就写了下面这一堆代码。
List<Integer> numbers = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
Map<Integer, List<Integer>> result =
numbers.stream()
// 按索引分组
.collect(
Collectors.groupingBy(
// 每两个元素为一组(0-1, 2-3, ...)
i -> i / 2,
// 映射原始数组元素到分组中
Collectors.mapping(
numbers::get,
// 收集为列表
Collectors.toList())));
// 输出 [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]
System.out.println(result.values());
说能用勉强也能将就,但现在有更好的选择了。
关于流收集器
JEP 485扩展了 Java 的 Stream API,允许开发者通过自定义中间操作(称为“收集器”)实现更灵活、更具表现力的数据处理。主要改进包括:
简而言之就是,现在可以在流的中间增加自定义操作(有几个的内置收集器),方便使用,提高效率。
使用流收集器重写一下
改写后如下:
List<List<Integer>> result =
Stream.iterate(0, i -> i + 1)
.limit(10)
.gather(Gatherers.windowFixed(2))
.toList();
是不是简洁多了?
内置的流收集器
官方内置了如下5个流收集器:
1. fold:一个有状态的多对一收集器,以增量方式构造聚合。
有状态的多对一收集器,以增量方式构造聚合结果(如累加),最终输出单一聚合值。
fold 将流元素逐个累加,初始值为 0,最终输出总和。
Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.fold(() -> 0, Integer::sum))
.forEach(System.out::println);
// 输出: 15
2. mapConcurrent:一个有状态的一对一收集器,同时为每个元素调用函数,直到达到限制。
每个元素乘以2:
Stream.iterate(0, i -> i + 1)
.limit(5)
.gather(Gatherers.mapConcurrent(3, (a) -> a * 2))
.forEach(System.out::println);
/*
输出:
0
2
4
6
8
*/
并发处理每个元素,并保持顺序。
3. scan:有状态的一对一收集器,应用函数到当前状态和元素生成下一个元素。
计算累积阶乘(1→2→6→24→120)。
Stream.iterate(1, i -> i + 1)
.limit(5)
.gather(Gatherers.scan(() -> 1, (acc, i) -> acc * i))
.forEach(System.out::println);
// 输出: 1, 2, 6, 24, 120
4. windowFixed:多对多收集器,将元素分组到固定大小的窗口,满时发射。
每3个元素为一组,输出子列表。
Stream.iterate(0, i -> i + 1)
.limit(8)
.gather(Gatherers.windowFixed(3))
.forEach(System.out::println);
// 输出: [0,1,2], [3,4,5], [6,7]
5. windowSliding:多对多收集器,滑动窗口,每个窗口删除第一个元素,添加下一个。
窗口大小为3,每次滑动一位(如从[0,1,2]到[1,2,3])。
Stream.iterate(0, i -> i + 1)
.limit(8)
.gather(Gatherers.windowSliding(3))
.forEach(System.out::println);
// 输出: [0,1,2], [1,2,3], [2,3,4], [3,4,5], [4,5,6], [5,6,7]
常用的方式总结
学会了吗?