Collectors类提供了很多方便的方法,假如现有的实现不能满足需求,我们如何自定义一个Collector呢?


  Collector接口提供了一个of方法,调用该方法就可以实现定制Collector。

    public static<T, A, R> Collector<T, A, R> of(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Function<A, R> finisher,
Characteristics... characteristics) {
...
Set<Characteristics> cs = Collectors.CH_NOID;
if (characteristics.length > 0) {
cs = EnumSet.noneOf(Characteristics.class);
Collections.addAll(cs, characteristics);
cs = Collections.unmodifiableSet(cs);
}
return new Collectors.CollectorImpl<>(supplier, accumulator, combiner, finisher, cs);
}
  • supplier提供一个 A 类型的对象,用于保存累加操作的结果;
  • accumulator提供累加操作的实现,接收 T 类型输入数据和 A 类型对象,将计算结果保存到 A 类型对象;
  • combiner用于多个输入对象的合并,在普通串行(sequential)的情况下只有一个输入对象可以忽略,假如steam使用了并发操作(parallel)时就必须进行对象合并了;
  • finisher用于将输入参数类型 A 转化为我们最终需要的类型 R,假如A与R的类型一致,则无需转化,可以忽略finisher的设置;
  • characteristics用于指定操作的优化类型;

  值得注意的是,注释中要求提供的T对象参数的类型必须为可变的(mutable);

@param <A> the mutable accumulation type of the reduction operation (often hidden as an implementation detail)

  在自定义之前,我们先来看看Collectors类源代码是怎么实现Collector接口。以常用的toList方法为例,

static final Set<Collector.Characteristics> CH_ID = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH));

public static <T> Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new,
List::add,
(left, right) -> {
left.addAll(right);
return left;
},
CH_ID);
}

  在toList方法中,supplier为ArrayList::new,构造一个ArrayList对象用于累加的容器,使用List::add作为accumulator累加操作,combiner实现中调用List的addAll

合并两个列表,由于最终类型就是List,因此toList忽略finisher,使用IDENTITY_FINISH优化类型,标明不需要进行finisher转换类型,直接返回计算结果。


  参考Collectors类方法的实现,我们来自定义一个Collector。假设有个需求,存在一个包含多个A类型对象的列表,要求计算该列表所有A对象属性count的总和。

 public class A {

    private int count;

    public A(int count) {
this.count = count;
} public int getCount() {
return count;
}
}

  对于该需求,一般可以简单的使用mapToInt或者Collectors类提供的summingInt方法实现。

var aList = new ArrayList<A>();
...
int totalUseMap = aList.stream().mapToInt(A::getCount).sum();
// or
int totalUseCollect = aList.stream().collect(Collectors.summingInt(Obj::getCount));

  但是假如使用自定义Collector的话,我们应该如何实现呢?方法如下:

var aList = new ArrayList<A>();
...
int total = aList.parallelStream().collect(Collector.of(() -> new int[1],
(result, a) -> result[0] += a.getCount(),
(a, b) -> {
a[0] += b[0];
return a;
},
result -> result[0],
Collector.Characteristics.CONCURRENT));

  supplier必须提供可变类型对象,所以不能简单的提供() -> 0,因为int是不可变的类型。假如使用int类型,会导致返回结果还是提供int对象的初始值,无法完成需求。

此外计算结果类型要求为int,与提供的累加对象int[]类型不一致,因此我们通过finisher函数将int[]类型的对象转化为int类型数据返回。accumulator的计算过程并不影响并

发,设置characteristics参数为CONCURRENT,支持并发操作,提高性能。

如何自定义一个Collector的更多相关文章

  1. 怎么在java中创建一个自定义的collector

    目录 简介 Collector介绍 自定义Collector 总结 怎么在java中创建一个自定义的collector 简介 在之前的java collectors文章里面,我们讲到了stream的c ...

  2. Java 8手动实现一个Collector

    我们看一下Stream中的collect的方法. collect(toList())方法由Stream里的值生成一个列表,是一个及早求值的操作. Stream的of方法使用一个初始值生成新的Strea ...

  3. SpringMVC 自定义一个拦截器

    自定义一个拦截器方法,实现HandlerInterceptor方法 public class FirstInterceptor implements HandlerInterceptor{ /** * ...

  4. jQuery Validate 表单验证插件----自定义一个验证方法

    一.下载依赖包 网盘下载:https://yunpan.cn/cryvgGGAQ3DSW  访问密码 f224 二.引入依赖包 <script src="../../scripts/j ...

  5. Spring自定义一个拦截器类SomeInterceptor,实现HandlerInterceptor接口及其方法的实例

    利用Spring的拦截器可以在处理器Controller方法执行前和后增加逻辑代码,了解拦截器中preHandle.postHandle和afterCompletion方法执行时机. 自定义一个拦截器 ...

  6. JSTL,自定义一个标签的功能案例

    1.自定义一个带有两个属性的标签<max>,用于计算并输出两个数的最大值: 2.自定义一个带有一个属性的标签<lxn:readFile  src=“”>,用于输出指定文件的内容 ...

  7. 自定义View(7)官方教程:自定义View(含onMeasure),自定义一个Layout(混合组件),重写一个现有组件

    Custom Components In this document The Basic Approach Fully Customized Components Compound Controls ...

  8. Volley HTTP库系列教程(5)自定义一个Volley请求

    Implementing a Custom Request Previous  Next This lesson teaches you to Write a Custom Request parse ...

  9. 在String()构造器不存在的情况下自定义一个MyString()函数,实现如下内建String()方法和属性:

    在String()构造器不存在的情况下自定义一个MyString()函数,实现如下内建String()方法和属性: var s = new MyString("hello"); s ...

随机推荐

  1. Kubernetes Job Controller 原理和源码分析(三)

    概述Job controller 的启动processNextWorkItem()核心调谐逻辑入口 - syncJob()Pod 数量管理 - manageJob()小结 概述 源码版本:kubern ...

  2. Eureka服务下线太慢,电话被告警打爆了

    某年某月的某一天,就像一张破碎的脸... 错了,重来. 某天,忽然发现大量的告警,经过多番调查研究考察(此处省略3000字),发现是由于 Eureka 服务下线太慢,而仍然有大量的请求打进来导致的报错 ...

  3. 一个ES设置操作引发的“血案”

    背景说明 ES版本 7.1.4 在ES生产环境中增加字段,一直提示Setting index.mapper.dynamic was removed after version 6.0.0错误.但是我只 ...

  4. Java使用FreeMarker模版技术动态生成word实践

    一.序言 在日常开发中,常常有动态word文件生成的需求,通过编制模版,然后动态修改word内容以组合成新的文件.报告单.请假单.发票页等都可以使用动态生成word来解决. 笔者总结归纳出通用技术要点 ...

  5. 聊聊 C++ 和 C# 中的 lambda 玩法

    这几天在看 C++ 的 lambda 表达式,挺有意思,这个标准是在 C11标准 加进去的,也就是 2011 年,相比 C# 2007 还晚了个 4 年, Lambda 这东西非常好用,会上瘾,今天我 ...

  6. Amazon 消息订阅对接

    亚马逊的api 谁用谁知道...... 除了坑还是坑 头疼一周整出来,分享给铁汁们 amazon 的订阅思维,我只能说外国人脑回路有点长 下面就讲讲具体流程步骤: 第一步: 参照官方教程:设置通知(A ...

  7. docker 操作 记录

    docker ps  #查看当前docker容器 docker exec -it  容器名称 sh  进入docker容器 docker stop 停止docker容器

  8. WPF中Popup控件的使用

    一.Popup控件的主要属性 Popup表示具有内容的弹出窗口,其主要属性为: Child:获取或设置 Popup控件的内容. IsOpen:获取或设置一个值,该值指示Popup 是否可见 Place ...

  9. CentOS 7 快速安装docker-compose

    安装docker-composegithub的地址下载太慢了,国内可以使用http://get.daocloud.io/#install-compose网站上面的地址. 首先下载docker-comp ...

  10. Halcon 条形码识别

    read_image (Image, 'C:/Users/HJ/Desktop/test_image/image.png') create_bar_code_model([], [], BarCode ...