文章目录

  • Pre
  • Collector接口声明的方法
  • 理解 Collector接口中声明的方法
    • 1.建立新的结果容器: supplier 方法
    • 2.将元素添加到结果容器: accumulator 方法
    • 3.对结果容器应用最终转换: finisher 方法
    • 4.合并两个结果容器: combiner 方法
    • 5. characteristics 方法
  • 自定义Collector Demo


Pre

Collector 接口包含了一系列方法,为实现具体的归约操作(即收集器)提供了范本。

我们已经看过了 Collector 接口中实现的许多收集器,例如 toList 或 groupingBy 。这也意味着可以为 Collector 接口提供自己的实现,从而自由地创建自定义归约操作。

要开始使用 Collector 接口,我们先看看toList 工厂方法,它会把流中的所有元素收集成一个 List 。我们当时说在日常工作中经常会用到这个收集器,而且它也是写起来比较直观的一个,至少理论上如此。通过仔细研究这个收集器是怎么实现的。

我们可以很好地了解 Collector 接口是怎么定义的,以及它的方法所返回的函数在内部是如何为collect 方法所用的。


Collector接口声明的方法

首先让我们在下面的列表中看看 Collector 接口的定义,它列出了接口的签名以及声明的五个方法。

  • T 是流中要收集的项目的泛型。
  • A 是累加器的类型,累加器是在收集过程中用于累积部分结果的对象。
  • R 是收集操作得到的对象(通常但并不一定是集合)的类型。

例如,你可以实现一个 ToListCollector<T> 类,将 Stream<T> 中的所有元素收集到一个List<T> 里,它的签名如下

public class ToListCollector<T> implements Collector<T, List<T>, List<T>>

待会揭秘 这里用于累积的对象也将是收集过程的最终结果


理解 Collector接口中声明的方法

现在我们可以一个个来分析Collector 接口声明的五个方法了。通过分析,你会注意到,前四个方法都会返回一个会被 collect 方法调用的函数,而第五个方法 characteristics 则提供了一系列特征,也就是一个提示列表,告诉 collect 方法在执行归约操作的时候可以应用哪些优化(比如并行化)。

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

supplier 方法必须返回一个结果为空的 Supplier ,也就是一个无参数函数,在调用时它会创建一个空的累加器实例,供数据收集过程使用。

很明显,对于将累加器本身作为结果返回的收集器,比如我们的 ToListCollector ,在对空流执行操作的时候,这个空的累加器也代表了收集过程的结果。

在我们的 ToListCollector 中, supplier 返回一个空的 List ,如下所示:

public Supplier<List<T>> supplier() {return () -> new ArrayList<T>();
}

请注意你也可以只传递一个构造函数引用:

public Supplier<List<T>> supplier() {return ArrayList::new;
}


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

accumulator 方法会返回执行归约操作的函数。当遍历到流中第n个元素时,这个函数执行时会有两个参数:保存归约结果的累加器(已收集了流中的前 n-1 个项目),还有第n个元素本身。

该函数将返回 void ,因为累加器是原位更新,即函数的执行改变了它的内部状态以体现遍历的元素的效果。

对于 ToListCollector ,这个函数仅仅会把当前项目添加至已经遍历过的项目的列表:

public BiConsumer<List<T>, T> accumulator() {return (list, item) -> list.add(item);
}

你也可以使用方法引用,这会更为简洁:

public BiConsumer<List<T>, T> accumulator() {return List::add;
}


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

在遍历完流后, finisher 方法必须返回在累积过程的最后要调用的一个函数,以便将累加器对象转换为整个集合操作的最终结果。

通常,就像 ToListCollector 的情况一样,累加器对象恰好符合预期的最终结果,因此无需进行转换。所以 finisher 方法只需返回 identity 函数:

public Function<List<T>, List<T>> finisher() {return Function.identity();
}

这三个方法已经足以对流进行顺序归约,至少从逻辑上看可以按下图进行。

实践中的实现细节可能还要复杂一点,一方面是因为流的延迟性质,可能在 collect 操作之前还需要完成其他中间操作的流水线,另一方面则是理论上可能要进行并行归约。


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

四个方法中的最后一个—— combiner 方法会返回一个供归约操作使用的函数,它定义了对
流的各个子部分进行并行处理时,各个子部分归约所得的累加器要如何合并。

对于 toList 而言,这个方法的实现非常简单,只要把从流的第二个部分收集到的项目列表加到遍历第一部分时得到的列表后面就行了:

public BinaryOperator<List<T>> combiner() {return (list1, list2) -> {list1.addAll(list2);return list1; }
}

有了这第四个方法,就可以对流进行并行归约了。它会用到Java 7中引入的分支/合并框架和Spliterator 抽象, 如下图所示

  1. 原始流会以递归方式拆分为子流,直到定义流是否需要进一步拆分的一个条件为非(如果分布式工作单位太小,并行计算往往比顺序计算要慢,而且要是生成的并行任务比处理器内核数多很多的话就毫无意义了)。
  2. 现在,所有的子流都可以并行处理,即对每个子流应用【见顺序归约过程的逻辑步骤】的顺序归约算法。
  3. 最后,使用收集器 combiner 方法返回的函数,将所有的部分结果两两合并。这时会把原始流每次拆分时得到的子流对应的结果合并起来。


5. characteristics 方法

最后一个方法—— characteristics 会返回一个不可变的 Characteristics 集合,它定义了收集器的行为——尤其是关于流是否可以并行归约,以及可以使用哪些优化的提示。

Characteristics 是一个包含三个项目的枚举。

  • UNORDERED ——归约结果不受流中项目的遍历和累积顺序的影响
  • CONCURRENT —— accumulator 函数可以从多个线程同时调用,且该收集器可以并行归约流。如果收集器没有标为 UNORDERED ,那它仅在用于无序数据源时才可以并行归约。
  • IDENTITY_FINISH ——这表明完成器方法返回的函数是一个恒等函数,可以跳过。这种情况下,累加器对象将会直接用作归约过程的最终结果。这也意味着,将累加器 A 不加检查地转换为结果 R 是安全的。

我们迄今开发的 ToListCollector 是 IDENTITY_FINISH 的,因为用来累积流中元素的List 已经是我们要的最终结果,用不着进一步转换了,但它并不是 UNORDERED ,因为用在有序流上的时候,我们还是希望顺序能够保留在得到的 List 中。

最后,它是CONCURRENT 的,但我们刚才说过了,仅仅在背后的数据源无序时才会并行处理。


自定义Collector Demo

public class ToListCollector<T> implements Collector<T, List<T>, List<T>> {private void log(final String log) {System.out.println(Thread.currentThread().getName() + "-" + log);}@Overridepublic Supplier<List<T>> supplier() {log("supplier");return ArrayList::new;}@Overridepublic BiConsumer<List<T>, T> accumulator() {log("accumulator");return List::add;}@Overridepublic BinaryOperator<List<T>> combiner() {log("combiner");return (list1, list2) -> {list1.addAll(list2);return list1;};}@Overridepublic Function<List<T>, List<T>> finisher() {log("finisher");return t -> t;}@Overridepublic Set<Characteristics> characteristics() {log("characteristics");return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH, Characteristics.CONCURRENT));}


测试下

public class CustomerCollectorAction {public static void main(String[] args) {Collector<String, List<String>, List<String>> collector = new ToListCollector<>();String[] arrs = new String[]{"Artisan", "Yang", "Hello", "Lambda", "Collector", "Java 8", "Stream"};List<String> result = Arrays.asList( arrs).parallelStream().filter(s -> s.length() >= 5).collect(collector);System.out.println(result);}
}

Java 8 - 自定义Collector相关推荐

  1. java 自定义 operator_java8 自定义Collector

    package com.lgx.jdk8.part02; import java.util.*; import java.util.function.BiConsumer; import java.u ...

  2. Java 8 自定义流Collector实现

    Java 8 自定义流Collector实现 前文我们看到 Java 8 Collectors提供了很多内置实现.但有时我们需要实现一些特定功能满足业务需要,本文带你学习如何自定义Collector的 ...

  3. Java8中Collector详解及自定义Collector

    文章目录 1.Collector介绍 2.Collector约束 3.Collector接口方法 4.理解Collector接口声明的方法 5.整合自定义Collector 6.使用collect方法 ...

  4. 【java8】自定义Collector

    Collector接口包含了一系列方法,为实现具体的归约操作(即收集器)提供了范本.我们已经看过了Collector接口中实现的许多收集器(由Collector接口的工具类Collectors提供), ...

  5. java 怎么自定义排序_Java如何实现List自定义排序

    Java如何实现List自定义排序,自定义,即为,详细内容,相关文章,更多关于 Java如何实现List自定义排序 易采站长站,站长之家为您整理了Java如何实现List自定义排序的相关内容. 实体类 ...

  6. java简单自定义Annotation

    为什么80%的码农都做不了架构师?>>>    原文内容比较多,这里就简单地说一下.java 5以后增加了3个annotation, @Override @Deprecated @S ...

  7. Robot Framework自动化测试框架核心指南-如何使用Java编写自定义的RobotFramework Lib

    如何使用Java编写自定义的RobotFramework Lib 本文包括2个章节 1. Robot Frdamwork中如何调用java Lib库 2.使用 java编写自定义的Lib 本文作者为: ...

  8. Java Map 自定义排序

    HashMap是不保证顺序的,要有顺序,要用LinkedHashMap,这是按照插入顺序排列的. Map接口的哈希表和链接列表实现,具有可预知的迭代顺序.此实现与HashMap的不同之处在于,后者维护 ...

  9. java8 两个list合并_深入介绍和使用 Java 8 的 Collector 接口和 Collectors 工具类

    1 概述 java.util.stream.Collector: Collector 接口用于将 Stream 流中的数据加工,转换,处理,最后返回结果. java.util.stream.Colle ...

最新文章

  1. linux vg主备机同步,切换VG主备链路(简)
  2. LeetCode Algorithm 204. 计数质数
  3. 使用Moq、NUnit和Shoulded进行单元测试
  4. Jenkins 流水线 获取git 分支列表_持续集成工具Jenkins看这篇就够啦!
  5. iOS的帮助网站(企业包发布)adhoc的测试
  6. 使用windows Phone 集成横幅广告教程
  7. WPF 引用 ttf文件
  8. Java的三大基本特征及其特点
  9. 客户信息管理系统----Java实现(以对象数组为主)
  10. 《时代》杂志:元宇宙将如何塑造我们的未来?
  11. RAID和LVM磁盘阵列技术
  12. 磁悬浮matlab,磁悬浮小球matlab
  13. 谷歌支付:In-app billing error: Null data in IAB activity result (-1002 )
  14. java计算机毕业设计基于springboo+vue的共享单车自行车管理系统
  15. Mac平台上有哪些好用的常用软件?
  16. PMP是不是要考第七版了?
  17. 三国刘备十大名言:三分天下要靠“混
  18. 空调外机冷媒充注快速密封接头应用案例
  19. maven系列(二)——maven应用分析
  20. VMware Workstation 虚拟机安装

热门文章

  1. terminal怎么运行c语言文件,在mac电脑的terminal里该如何运行c语言
  2. php 新闻列表,php原生开发新闻站之新闻列表(二)
  3. tf.lookup.StaticHashTable 用法
  4. linux mpich配置_Linux下安装MPICH
  5. 将搜索二叉树转换成双向链表
  6. NTU 笔记 6422quiz 复习(1~3节)
  7. 文巾解题 797. 所有可能的路径
  8. 文巾解题 13. 罗马数字转整数
  9. Java高阶部分知识点汇总(二)-封装与隐藏详讲
  10. Flink从入门到精通100篇(二)-在Linux中完整安装flink并做Flink文件的配置