前言

Stream的基本操作因为平时工作中用得非常多(也能看到一些同事把Stream操作写得很丑陋),所以基本用法就不写文章记录了。

之所以能把Stream的操作写得很丑陋,完全是因为Stream底层的一些东西不太明白。自己也需要注意。

本文就是介绍Collector的基本原理。以便加深自己的记忆。

自己对这个接口的定义(很强势):收集器、收集器,就是按照一定的规则(你可以任意实现它),把一个流里面的数据收集到一个容器里。

Collector接口源码与定义

我就不挨着挨着翻译源码里的注释了,太多。记一下自己的理解即可。

package java.util.stream;

public interface Collector<T, A, R> {
Supplier<A> supplier();
BiConsumer<A, T> accumulator();
BinaryOperator<A> combiner();
Function<A, R> finisher(); enum Characteristics {
CONCURRENT,
UNORDERED,
IDENTITY_FINISH
}
}

Collector接口中有四个方法定义(每个方法的返回值都是一个JDK8默认的函数式接口)和一个枚举。

四大方法

  • Supplier<A> supplier()

    中文翻译:提供者

    在Collector中,这个方法的意义是返回一个可变的结果容器(result container),而是这个可变的结果容器(result container)的获取行为(别人毕竟是返回的Supplier对象),这里解释两点

    1)为什么叫结果容器?因为根据前言中自己的定义,需要一个容器来装东西。

    2)为什么是可变的?我们知道,流是由流源/中间操作/终止操作组合而构成的一个双向链表,只有终止操作执行的时候,才会把流源里的集合的数据执行中间操作和终止操作(忘记了,可以回忆下我的这篇Stream中的Pipeline理解)。所以当触发流的执行的时候,是一个一个从原始容器(流源中的)往新的结果容器中添加的,所以是可变的。

  • BiConsumer<A, T> accumulator()

    中文翻译:累加器

    回顾一下JDK 8中重要的函数式接口(必知必会)中的双参消费者,明白了这个累加器代表接收两个参数,返回void的行为(为什么老是强调行为行为,是因为函数式接口就代表一个行为,而不是简单意义的对象,虽然本质上是,但函数式编程的精髓不能这么理解。这个行为可以被传递,也可以被客户端实现)。

    这里简单说下返回值BiConsumer<A, T>,在收集器收集的过程中执行的是:【a1是结果容器,t1是流源的遍历对象】

A a1 = supplier.get();
accumulator.accept(a1, t1);
  • BinaryOperator<A> combiner()

    中文翻译:组合器

    回顾一下JDK 8中重要的函数式接口(必知必会)中的同类型双参操作,代表接收两个同类型参数,返回一个同类型参数

    如果一个流是并行流,它做收集的时候是基于JDK7的ForkJoin框架来分解与合并任务的,这个组合器就是对于并行流起作用的。

    后续再分析,目前我对ForkJoin也懂得不全面。

  • Function<A, R> finisher()

    中文翻译:终结者

    返回一个函数f(x)=y,输入一个参数,返回一个参数。

    对串行流来说,在所有的累加完成之后,把结果容器a1转换为r1的行为。

    对并行流来说,在组合完成之后,把组合结果x1转换为r1的行为。

talk is cheap,show me the code 不过这不是code,是JDK源码中的注释。我觉得这说明了一切。

      A a1 = supplier.get();
accumulator.accept(a1, t1);
accumulator.accept(a1, t2);
R r1 = finisher.apply(a1); // result without splitting A a2 = supplier.get();
accumulator.accept(a2, t1);
A a3 = supplier.get();
accumulator.accept(a3, t2);
R r2 = finisher.apply(combiner.apply(a2, a3)); // result with splitting

三大泛型

下面几行来自于JDK源码中的注释,从上面几行的代码也可以:

  • @param <T> the type of input elements to the reduction operation,输入过来的元素的类型
  • @param <A> the mutable accumulation type of the reduction operation (often hidden as an implementation detail) 结果容器的类型
  • @param <R> the result type of the reduction operation 执行最后终结操作的返回值

例子:

User类中有几个属性:id、username

如果流源是List,结果容器是ArrayList,收集器目的是收集所有用户的用户名,那么终结者的返回值就也是ArrayList.

那么:

<T> 为User

<A> 为ArrayList

<R> 为ArrayList

Characteristics枚举类

    enum Characteristics {
CONCURRENT,
UNORDERED,
IDENTITY_FINISH
}

下面几句话是我自己翻译的JDK源码的注释,如有错误请指正

  • Characteristics代表着可以在优化规约操作的一些特性。收集器可以指明这些特性。
  • CONCURRENT特性:代表这个收集器支持并发,这里并发指可以支持并发(多线程)的对同一个result container进行“累加操作”
  • 如果一个收集器具有CONCURRENT特性,但是没有UNORDERED特性,就意味着:这个收集器只会在应用到一个无序数据源(如Set)时,才会被并发的执行收集过程。
  • UNORDERED特性:代表这个收集器在收集时不会保留它遇到的元素的顺序,如果一个收集器的结果容器(result container)是一个Set这样的无序容器,那么应该给你的结果容器设置UNORDERED这个特性。
  • IDENTITY_FINISH特性:代表这个终结者返回的Function对象是identity function.也就代表着如果设置了这个特性,将三大泛型中的泛型A转换为R不需要检查,必须被执行成功。

Collector的同一性(Identity)与结合性(Associativity)

查看Collector的JDK源码文档可以看到

To ensure that sequential and parallel executions produce equivalent results, the collector functions must satisfy an identity and an associativity constraints.
为了确保串行和并行的执行得到相同的结果,收集器必须满足2个条件:同一性(identity)和结合性(associativity) The identity constraint says that for any partially accumulated result, combining it with an empty result container must produce an equivalent result. That is, for a partially accumulated result a that is the result of any series of accumulator and combiner invocations, a must be equivalent to combiner.apply(a, supplier.get()).
同一性:在任何累加过程中,中间结果容器和一个空的结果容器合并,应该等于当前这个中间结果容器。即:a = combiner.apply(a, supplier.get()) The associativity constraint says that splitting the computation must produce an equivalent result. That is, for any input elements t1 and t2, the results r1 and r2 in the computation below must be equivalent:
结合性:指对源数据的分割的计算结果等价于不分割的计算结果。即:如下面代码的r1和r2应该相等。 A a1 = supplier.get();
accumulator.accept(a1, t1);
accumulator.accept(a1, t2);
R r1 = finisher.apply(a1); // result without splitting A a2 = supplier.get();
accumulator.accept(a2, t1);
A a3 = supplier.get();
accumulator.accept(a3, t2);
R r2 = finisher.apply(combiner.apply(a2, a3)); // result with splitting

然后JDK官方文档链接了结合性的说明:JDK官方的结合性说明

Associativity
An operator or function op is associative if the following holds: (a op b) op c == a op (b op c) The importance of this to parallel evaluation can be seen if we expand this to four terms: a op b op c op d == (a op b) op (c op d) So we can evaluate (a op b) in parallel with (c op d), and then invoke op on the results.
Examples of associative operations include numeric addition, min, and max, and string concatenation.

Collector的复合(Composed)

Collectors are designed to be composed; many of the methods in Collectors are functions that take a collector and produce a new collector. For example, given the following collector that computes the sum of the salaries of a stream of employees :
Collector设计出来是支持复合的,在工具类java.util.stream.Collectors类中有很多方法,可以传入一个Collector,得到一个新的Collector。 先创建一个收集员工的工资总额的收集器。
Collector<Employee, ?, Integer> summingSalaries = Collectors.summingInt(Employee::getSalary)) 然后,创建一个新的收集器,按照部门分组,计算所有员工的工资总额。就复用了summingSalaries收集器。
If we wanted to create a collector to tabulate the sum of salaries by department, we could reuse the "sum of salaries" logic using Collectors.groupingBy(Function, Collector): Collector<Employee, ?, Map<Department, Integer>> summingSalariesByDept
= Collectors.groupingBy(Employee::getDepartment, summingSalaries);

自定义收集器实现

举一个自己工作中的例子,当时也是为了使用自定义的收集器而使用,后来发现,把收集逻辑独立出来,创建一个自定义收集器,在项目的深入进行,这些逻辑也是可以复用的。

所以以后有自定义收集的过程的时候,一定要单独把逻辑独立出来。

public class YourLogicCollector
implements Collector<你自己的流源输入对象, Set<String>, Set<String>> { static final Set<Collector.Characteristics> CH_UNORDERED_ID
= Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.UNORDERED,
Collector.Characteristics.IDENTITY_FINISH)); @Override
public Supplier<Set<String>> supplier() {
return HashSet::new;
} @Override
public BiConsumer<Set<String>, 你自己的流源输入对象> accumulator() {
return (set, data) -> set.addAll(你自己的逻辑(data));
} @Override
public BinaryOperator<Set<String>> combiner() {
return (left, right) -> {
left.addAll(right);
return left;
};
} @Override
public Function<Set<String>, Set<String>> finisher() {
return t -> t;
} @Override
public Set<Characteristics> characteristics() {
return CH_UNORDERED_ID;
} private Set<String> 你自己的逻辑(YourInputEntity entity) {
return 你自己的收集逻辑;
}
}

Stream中的Collector收集器原理的更多相关文章

  1. Java8中重要的收集器Collector

    Collector介绍 Java8的stream api能很方便我们对数据进行统计分类等工作,函数式编程的风格让我们方便并且直观地编写统计代码. 例如: Stream<Integer> s ...

  2. java8学习之Collector源码分析与收集器核心

    之前已经对流在使用上已经进行了大量应用了,也就是说对于它的应用是比较熟悉了,但是比较欠缺的是对于它底层的实现还不太了解,所以接下来准备大量通过阅读官方的javadoc反过来加深对咱们已经掌握这些知识更 ...

  3. Java8学习笔记(十)--自定义收集器

    前言 以前写过Java8中的自定义收集器,当时只是在文章末尾放了个例子,觉得基本用法挺简单,而且有些东西没搞懂(比如combiner方法到底做什么的),没有专门写,过了一段时间又忘了,所以,即使还是没 ...

  4. JVM中常见的垃圾收集器

    垃圾收集机制是 Java 的招牌能力,极大地提高了开发效率.如今,垃圾收集几乎成为现代语言的标配,即使经过如此长时间的发展, Java 的垃圾收集机制仍然在不断的演进中,不同大小的设备.不同特征的应用 ...

  5. java8学习之自定义收集器实现

    在上次花了几个篇幅对Collector收集器的javadoc进行了详细的解读,其涉及到的文章有: http://www.cnblogs.com/webor2006/p/8311074.html htt ...

  6. JVM实用参数(七)CMS收集器

    HotSpot JVM的并发标记清理收集器(CMS收集器)的主要目标就是:低应用停顿时间.该目标对于大多数交互式应用很重要,比如web应用.在我们看一下有关JVM的参数之前,让我们简要回顾CMS收集器 ...

  7. 学习JVM--垃圾回收(二)GC收集器

    1. 前言 在上一篇文章中,介绍了JVM中垃圾回收的原理和算法.介绍了通过引用计数和对象可达性分析的算法来筛选出已经没有使用的对象,然后介绍了垃圾收集器中使用的三种收集算法:标记-清除.标记-整理.标 ...

  8. 学习JVM-GC收集器

    1. 前言 在上一篇文章中,介绍了JVM中垃圾回收的原理和算法.介绍了通过引用计数和对象可达性分析的算法来筛选出已经没有使用的对象,然后介绍了垃圾收集器中使用的三种收集算法:标记-清除.标记-整理.标 ...

  9. JDK1.7 Update14 HotSpot虚拟机GC收集器

    在测试服务器上使用如下命令可以查看当前使用的 GC收集器,当然不止这一个命令可以看到,还有其他一些方式 第三列”=”表示第四列是参数的默认值,而”:=” 表明了参数被用户或者JVM赋值了 [csii@ ...

随机推荐

  1. elsa-core:4.ASP.NET Core Server with Elsa Dashboard

    在本快速入门中,我们将了解一个最小的 ASP.NET Core 应用程序,该应用程序承载 Elsa Dashboard 组件并将其连接到 Elsa Server. ElsaDashboard + Do ...

  2. list类型数据的操作指令

    1. 结果是 3 2 1 还可以继续追加如下: 2. 3. 4.删除表头元素(最左侧的元素),并返回该元素 5. 6. 7.删除表尾的元素(最右侧的元素),并返回该元素 8.

  3. MySQL——企业SQL优化方案

    一.大表 (1)列多: 纵向拆分大表: create t1; insert into t1 select id, name from test; (2)行多: 根据数据存放特点和逻辑进行横向拆分大表: ...

  4. Selenium系列(22) - 通过selenium控制浏览器滚动条的几种方式

    如果你还想从头学起Selenium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1680176.html 其次,如果你不懂前端基础知识, ...

  5. go实现堆排序、快速排序、桶排序算法

    一. 堆排序 堆排序是利用堆这种数据结构而设计的一种排序算法.以大堆为例利用堆顶记录的是最大关键字这一特性,每一轮取堆顶元素放入有序区,就类似选择排序每一轮选择一个最大值放入有序区,可以把堆排序看成是 ...

  6. Request 获取根据页面获取用户输入判断登陆成功或者失败

    import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.serv ...

  7. windows中对文件进行排序

    右键->排序方式->更多->选择需要的项目

  8. kubectl apply部署时可以用 --record 方便记录版本 和回退

    1.部署时正常时下面的 kubectl apply -f http.yaml 2.如果修改文件文件重新部署或者之前有上一个版本的  想回退上一个的 可以无感知的回退回去 不影响业务 其中http-de ...

  9. vue-cli3 项目中通过 CDN方式 使用 echarts

    1.html 中引入 echarts         html中添加script标签如下:         <script src="//cdn.bootcss.com/echarts ...

  10. PHP中的MySQLi扩展学习(二)mysqli类的一些少见的属性方法

    虽说是少见的一些属性方法,但是可能还是有不少同学在日常的开发中使用过,这里只是学习了可能相对来说我们用得比较少的一些 mysqli 的属性或方法.就当是扩展一下自己的知识体系. 切换用户 首先就是切换 ...