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. 3000帧动画图解MySQL为什么需要binlog、redo log和undo log

    全文建立在MySQL的存储引擎为InnoDB的基础上 先看一条SQL如何入库的: 这是一条很简单的更新SQL,从MySQL服务端接收到SQL到落盘,先后经过了MySQL Server层和InnoDB存 ...

  2. Flink中如何实现一个自定义MetricReporter

    什么是 Metrics 在 flink 任务运行的过程中,用户通常想知道任务运行的一些基本指标,比如吞吐量.内存和 cpu 使用情况.checkpoint 稳定性等等.而通过 flink metric ...

  3. LVS+keepalived高可用

    1.keeplived相关 1.1工作原理 Keepalived 是一个基于VRRP协议来实现的LVS服务高可用方案,可以解决静态路由出现的单点故障问题. 在一个LVS服务集群中通常有主服务器(MAS ...

  4. TL,你是如何管理项目风险的?

    沙包和打伞的故事 美国在1961年到1972年组织实施的一系列载人登月飞行任务.目的是实现载人登月飞行和人对月球的实地考察,为载人行星飞行和探测进行技术准备,它是世界航天史上具有划时代意义的一项成就. ...

  5. 腾讯QQ快捷登陆

    腾讯QQ快捷 相关各语言对接qq快捷登录教程 [C#]QQ开放平台(QQ站外登录)_流程和源码示例 j2ee中实现QQ第三方登陆 web实现QQ第三方登录 asp.net网站接入QQ登录 php实现q ...

  6. Vue MD5加密你用吗?

    安装 npm install --save js-md5 1.按需引入(在你需要的项目中引入) 引入: import md5 from 'js-md5' 使用: md5('加密信息') 2.全局引入( ...

  7. python基础知识-day8(模块与包、random、os)

    1.模块与包 package:相同的模块代码存储在一个目录下(即包里边会包含多个模块).   包不能存储在文件夹的目录下,模块名称不能使用关键字.(不包含工程文件夹) 2.模块与包的实例 1)在工程文 ...

  8. Canal实现MySQL协议

    目录 代码流程 执行dump前 执行dump 在学习Canal的时候很好奇Canal是如何模拟成MySql Slave来接收数据的 MySql Slave会向主库发送dump协议来接收bin-log数 ...

  9. Flex & Bison 开始

    Flex 与 Bison 是为编译器和解释器的编程人员特别设计的工具: Flex 用于词法分析(lexical analysis,或称 scanning),把输入分割成一个个有意义的词块,称为记号(t ...

  10. DNS原理&ssh

    作用:实现域名的解析! www.baidu.com => 14.215.177.37 域名: www.baidu.com 实际域名为: www.baidu.com. 域名的解析,是反向的. 最后 ...