Java Stream 自定义Collector
Collector的使用
使用Java Stream流操作数据时,经常会用到各种Collector收集器来进行数据收集。
这里便深入了解一点去了解Collector的工作原理和如何自定义Collector。
使用例子为:
// String joining
String foodNameList1 = foodList.stream().map(Food::getSimpleName).collect(Collectors.joining(","));
String foodNameList2 = foodList.stream().map(Food::getSimpleName).reduce("", (a, b) -> String.join(",", a, b));
String foodNameList3 = foodList.stream().collect(Collectors.reducing("", Food::getSimpleName, (a, b) -> String.join(",", a, b)));
// group by operation
Map<String, Map<String, List<Food>>> cookingAndCategoryMap = foodList.stream().collect(Collectors.groupingBy(Food::getCookingStyle, HashMap::new, Collectors.groupingBy(Food::getCategory)));
Map<String, Food> cookingAndPriceMap = foodList.stream().collect(Collectors.groupingBy(Food::getCookingStyle, HashMap::new, Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(Food::getPrice)), Optional::get)));
Collector<T, A, R> 接口
Collector Interface 包含一系列方法,为实现具体的规约操作提供了范本。
我们可以通过实现Collector接口来自定义自己的收集器,从而可以自由地创建自定义规约操作。
要想自定义收集器,必然需要先理解Collector接口的定义。
其中接口泛型类定义如下:
-T是流中要收集的项目的泛型 。
-A是累加器的泛型,累加器在收集过程中用于累积部分结果。
-R是收集操作得到的对象的类型(通常是集合)。
Collector Interface 定义如下:
public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher();
Set<Characteristics> characteristics();
/**
* Characteristics indicating properties of a {@code Collector}, which can
* be used to optimize reduction implementations.
*/
enum Characteristics {
CONCURRENT,
UNORDERED,
IDENTITY_FINISH
}
}
Collector 接口方法
-
建立新的结果容器:supplier方法
supplier方法需返回一个Supplier,也就是一个无参数函数。
在调用时会创建一个空的累加器实例,供数据收集过程使用。
如Collectors.toList()中supplier实现为:
return ArrayList:new;
-
将元素添加到结果容器:accumulator方法
accumulator方法会返回执行规约操作的函数,每次执行函数都会更新累加器。
BiConsumer 无返回值,原位更新累加器。两个参数分别为保存规约结果的累加器和遍历元素。
如Collectors.toList()中accumulator方法实现为:
return List:add;
-
对结果容器应用最终转换:finisher方法
这是在遍历完流之后,在累积过程的最后要调用的一个函数,以便将累加器对象转换为整个集合操作的最终结果。
通常,累加器对象便是最终结果。如Collectors.toList()方法中finisher实现为:
return List:addAll;
-
合并两个结果容器:combiner方法
返回一个供规约操作使用的函数,定义了对流的各个子部分进行并行处理时,各个子部分要如何合并。
即将多个累加器合并为一个,如Collectors.toList()中combiner实现为:
return List:addALL
-
定义收集器的行为:characteristics方法
返回一份不可变的Characteristic集合,它定义了收集器的行为——关于流是否可以进行并行规约、可以使用那些优化的提示。总分包含三个部分:- UNORDERED——规约结果不受流中项目的遍历和累积顺序的影响
- CONCURRENT——accumulator函数可以从多个线程同时调用,且该收集器可以并行规约流。
- IDENTITY_FINSIH——表示完成器方法返回的函数是一个恒等函数,可以跳过。这种情况下,
累加器对象将会直接用作规约过程的最终结果。即不会将累加器A转化为结果R。
至此,Collector 接口定义的方法便全部了解了,使用前三个方法便能完成顺序流的规约,规约过程如下:
在前三个方法的基础上,再加上第四个方法便能支持并行流的规约,过程如下:
实现自定义Collector
了解完成Collector相关的接口方法定义和规约过程之后,我们便可以开始自定义Collector 实现了。
创建一个将String 元素放入LinkedList 的收集器,如下:
public class MyCollector implements Collector<String, List, List>{
@Override
public Supplier<List> supplier() {
return LinkedList::new;
}
@Override
public BiConsumer<List, String> accumulator() {
return List::add;
}
@Override
public BinaryOperator<List> combiner() {
return (r1, r2) -> {
r1.addAll(r2);
return r1;
};
}
@Override
public Function<List, List> finisher() {
return list -> list;
}
@Override
public Set<Characteristics> characteristics() {
return EnumSet.of(Characteristics.IDENTITY_FINISH);
}
}
List<String> simpleNameList = foodList.stream().map(Food::getSimpleName).collect(new MyCollector());
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~