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 接口方法

  1. 建立新的结果容器:supplier方法

    supplier方法需返回一个Supplier,也就是一个无参数函数。

    在调用时会创建一个空的累加器实例,供数据收集过程使用。

    如Collectors.toList()中supplier实现为:

    return ArrayList:new;

  2. 将元素添加到结果容器:accumulator方法

    accumulator方法会返回执行规约操作的函数,每次执行函数都会更新累加器。

    BiConsumer 无返回值,原位更新累加器。两个参数分别为保存规约结果的累加器和遍历元素。

    如Collectors.toList()中accumulator方法实现为:

    return List:add;

  3. 对结果容器应用最终转换:finisher方法

    这是在遍历完流之后,在累积过程的最后要调用的一个函数,以便将累加器对象转换为整个集合操作的最终结果。

    通常,累加器对象便是最终结果。如Collectors.toList()方法中finisher实现为:

    return List:addAll;

  4. 合并两个结果容器:combiner方法

    返回一个供规约操作使用的函数,定义了对流的各个子部分进行并行处理时,各个子部分要如何合并。

    即将多个累加器合并为一个,如Collectors.toList()中combiner实现为:

    return List:addALL

  5. 定义收集器的行为: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());

Java Stream 自定义Collector的更多相关文章

  1. java stream collector

    Java Stream API进阶篇 本文github地址 上一节介绍了部分Stream常见接口方法,理解起来并不困难,但Stream的用法不止于此,本节我们将仍然以Stream为例,介绍流的规约操作 ...

  2. java stream中Collectors的用法

    目录 简介 Collectors.toList() Collectors.toSet() Collectors.toCollection() Collectors.toMap() Collectors ...

  3. 深度掌握 Java Stream 流操作,让你的代码高出一个逼格!

    概念 Stream将要处理的元素集合看作一种流,在流的过程中,借助Stream API对流中的元素进行操作,比如:筛选.排序.聚合等. Stream 的操作符大体上分为两种:中间操作符和终止操作符 中 ...

  4. Java Stream 使用详解

    Stream是 Java 8新增加的类,用来补充集合类. Stream代表数据流,流中的数据元素的数量可能是有限的,也可能是无限的. Stream和其它集合类的区别在于:其它集合类主要关注与有限数量的 ...

  5. Java Stream函数式编程图文详解(二):管道数据处理

    一.Java Stream管道数据处理操作 在本号之前发布的文章<Java Stream函数式编程?用过都说好,案例图文详解送给你>中,笔者对Java Stream的介绍以及简单的使用方法 ...

  6. [源码解析] 当 Java Stream 遇见 Flink

    [源码解析] 当 Java Stream 遇见 Flink 目录 [源码解析] 当 Java Stream 遇见 Flink 0x00 摘要 0x01 领域 1.1 Flink 1.2 Java St ...

  7. Java Stream 源码分析

    前言 Java 8 的 Stream 使得代码更加简洁易懂,本篇文章深入分析 Java Stream 的工作原理,并探讨 Steam 的性能问题. Java 8 集合中的 Stream 相当于高级版的 ...

  8. Java Stream API性能测试

    已经对Stream API的用法鼓吹够多了,用起简洁直观,但性能到底怎么样呢?会不会有很高的性能损失?本节我们对Stream API的性能一探究竟. 为保证测试结果真实可信,我们将JVM运行在-ser ...

  9. java stream 原理

    java stream 原理 需求 从"Apple" "Bug" "ABC" "Dog"中选出以A开头的名字,然后从中选 ...

随机推荐

  1. DHCP的简单介绍与配置

    一.DHCP简介 二.DHCP报文类型 三.DHCP工作原理 四.实例操作 一.DHCP简介 DHCP(Dynamic Host Configuration Protocol),动态主机配置协议,是一 ...

  2. 【Azure 应用服务】Azure Function App 执行PowerShell指令[Get-Azsubscription -TenantId $tenantID -DefaultProfile $cxt]错误

    问题描述 使用PowerShell脚本执行获取Azure订阅列表的指令(Get-Azsubscription -TenantId $tenantID -DefaultProfile $cxt).在本地 ...

  3. PHP递归创建多级目录(一道面试题的解题过程)(转)

      今天看到一道面试题,要写出一个可以创建多级目录的函数: 我的第一个感觉就是用递归创建,具体思路如下: function Directory($dir){ if(is_dir($dir) || @m ...

  4. Oracle如何以逗号分隔的字符串拆分为多行数据

    近期在工作中遇到某表某字段是可扩展数据内容,信息以逗号分隔生成的,现需求要根据此字段数据在其它表查询相关的内容展现出来,第一想法是切割数据,以逗号作为切割符,以下为总结的实现方法,以供大家参考.指教. ...

  5. 不同版本docker修改存储位置补充

    前言:最近发现yum安装docker,安装的版本不一样,有点蛇皮,虽然存放默认位置都是/var/lib/docker,但是它的配置文件不一样,这里做个补充 对于docker版本是1.13及以下 操作如 ...

  6. 为什么使用 LSTM 训练速度远大于 SimpleRNN?

    今天试验 TensorFlow 2.x , Keras 的 SimpleRNN 和 LSTM,发现同样的输入.同样的超参数设置.同样的参数规模,LSTM 的训练时长竟然远少于 SimpleRNN. 模 ...

  7. P4827「国家集训队」 Crash 的文明世界

    「国家集训队」 Crash 的文明世界 提供一种不需要脑子的方法. 其实是看洛谷讨论版看出来的( (但是全网也就这一篇这个方法的题解了) 首先这是一个关于树上路径的问题,我们可以无脑上点分治. 考虑当 ...

  8. Linux- RPM与yum软件包安装

    Linux安装及管理程序一.Linux应用程序基础1)应用程序与系统命令的关系2)典型应用程序的目录结构3)常见的软件包封装类型二.RPM包管理工具① RPM软件包管理器Red-Hat Package ...

  9. C语言:函数

    1. int scanf ( char * format [ ,argument, ... ]);   返回被赋值的参数的个数

  10. C语言:条件编译

    假如现在要开发一个C语言程序,让它输出红色的文字,并且要求跨平台,在 Windows 和 Linux 下都能运行,怎么办呢?这个程序的难点在于,不同平台下控制文字颜色的代码不一样,我们必须要能够识别出 ...