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. Dubbo本地存根是什么,Dubbo本地伪装又是什么?

    真正的大师永远怀着一颗学徒的心 哈喽!大家好,我是小奇,一位程序员界的学徒 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 前言 书接上回,昨天打了 ...

  2. SQL Server之自动创建视图

    本方法只适合特定模式的视图创建. 比如,创建需要整张表列名的视图,或者当表和需要的列名统计在一张数据表当中,如图所示: 首先要先获取要创建视图所需要的表,这里我获取的是整个数据库中的表, IF OBJ ...

  3. 7. Docker CI、CD

    在上图这个新建的docker-compose.yml文件中把刚才的代码粘贴进去. 可把上述文件保存后,然后到/etc/ssh/sshd_config文件中更改下对应的端口号即可. 然后重新启动sshd ...

  4. swap函数模板

    在许多应用程序中,都有交换相同类型的两个变量内容的需要.例如,在对整数数组进行排序时,将需要一个函数来交换两个变量的值,如下所示: void swap(int &a, int &b) ...

  5. Linux查看系统参数配置

    Linux查看系统参数 1.查看内存(以GB为单位) [root@rac1 ~]# free -g total :内存总数,物理内存总数 used :已使用内存 free :空闲的内存数 shared ...

  6. 基于MybatisPlus代码生成器(2.0新版本)

    一.模块简介 1.功能亮点 实时读取库表结构元数据信息,比如表名.字段名.字段类型.注释等,选中修改后的表,点击一键生成,代码成即可提现出表结构的变化. 单表快速转化restful风格的API接口并对 ...

  7. JQuery实现图片轮播无缝滚动

    图片轮播无缝滚动实例 实现效果展示预览: 思路: 1.设置当前索引curIndex,和前一张索引prevIndex.(curIndex为下一次要显示的图片索引,prevIndex为现在看见的图片) 2 ...

  8. 迭代阈值收缩算法ISTA,背后的思想与具体推到过程

  9. springboot集成swagger2报Illegal DefaultValue null for parameter type integer

    springboot集成swagger2,实体类中有int类型,会报" Illegal DefaultValue null for parameter type integer"的 ...

  10. 分库分表真的适合你的系统吗?聊聊分库分表和NewSQL如何选择

    曾几何时,"并发高就分库,数据大就分表"已经成了处理 MySQL 数据增长问题的圣经. 面试官喜欢问,博主喜欢写,候选人也喜欢背,似乎已经形成了一个闭环. 但你有没有思考过,分库分 ...