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. Redis - 读写模式 - 缓存一致性

    Cache Aside Pattern(旁路缓存模式) 读:从cache中读取数据,若读取到则直接返回:cache中不存在则去database中读取,然后更新到cache. 写:先更新database ...

  2. 彰显个性│github和gitlab之自定义首页样式

    目录 一.个性首页 二.制作步骤 三.修改内容 一.个性首页 相信很多小伙伴在逛 github 和 gitlab 的时候 会发现很多开发者的首页异常的炫酷,如 https://github.com/c ...

  3. python中f'{}'用法

    python3.6增加的方法,字符串定义以f开头,可以使用{}包裹变量,方便字符串的定义. 有些时候很懒,碰到写的比较清晰的就直接搬运:https://blog.csdn.net/weixin_387 ...

  4. 介绍python和库文件管理

    一.Python 特点 1.易于学习:Python有相对较少的关键字,结构简单,和一个明确定义的语法,学习起来更加简单. 2.易于阅读:Python代码定义的更清晰. 3.易于维护:Python的成功 ...

  5. 设置C#启动进程但不显示命令行窗口

    设置一下Process类型相关的配置属性即可,直接上代码. //记得引入命名空间 //using System.Diagnostics; //获得当前环境的基路径 string basePath = ...

  6. go Cobra命令行工具入门

    简介 Github:https://github.com/spf13/cobra Star:26.5K   Cobra是一个用Go语言实现的命令行工具.并且现在正在被很多项目使用,例如:Kuberne ...

  7. 两分钟解决Python读取matlab的.mat数据

    Matlab是学术界非常受欢迎的科学计算平台,matlab提供强大的数据计算以及仿真功能.在Matlab中数据集通常保存为.mat格式.那么如果我们想要在Python中加载.mat数据应该怎么办呢?所 ...

  8. p_b_p_b 杂题选讲

    [ARC119F] AtCoder Express 3 [ARC117F] Gateau 考虑二分答案,对前缀和建差分约束 \(\text{check}\) ,但是用 \(\text{spfa}\) ...

  9. Tapdata 实时数据融合平台解决方案(五):落地

    作者介绍:TJ,唐建法,Tapdata 钛铂数据 CTO,MongoDB中文社区主席,原MongoDB大中华区首席架构师,极客时间MongoDB视频课程讲师. 通过前面几篇文章,我们从企业数据整合与分 ...

  10. throws关键字_异常处理的第一种方式(交给别人处理)和try_catch_异常处理的第二种方式(自己处理)

    throws关键字:异常处理的第一种方式,交给别人处理 作用: 当方法内部抛出异常对象的时候,那么我们就必须处理这个异常对象 可以使用throws关键字处理异常对象, 会把异常对象声明抛出给方法的调用 ...