前言

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. Structs2的作用是什么??

    struts2是一种重量级的框架,位于MVC架构中的controller,可以分析出来,它是用于接受页面信息然后通过内部处理,将结果返回. 同时struts2也是一个web层的MVC框架,那么什么是s ...

  2. 《DotNet Web应用单文件部署系列》一、pubxml文件配置

    很多人想用DotNet开发软件赚点外快子补添家用,但心里总放不下心来,担心被人破解了.好消息是去年发布的DotNet 5支持单文件部署,不同于DotNet 3运行时将文件释放到临时文件夹内,DotNe ...

  3. C# List集合类常用操作:三、查找

    List集合查询数据 List<Employees> employees = new List<Employees>(); employees.Add(new Employee ...

  4. elementui table的新增,编辑和删除

    \ 新增 this.tableData.unshift(data); 编辑 this.$set(this.tableData,data.index,data); 删除 rows.splice(inde ...

  5. [第六篇]——云服务器之Spring Cloud直播商城 b2b2c电子商务技术总结

    云服务器 云服务器(Elastic Compute Service, ECS)是一种简单高效.安全可靠.处理能力可弹性伸缩的计算服务. 云服务器管理方式比物理服务器更简单高效,我们无需提前购买昂贵的硬 ...

  6. 第十一章 Net 5.0 快速开发框架 YC.Boilerplate --图数据库模块Neo4j

    在线文档:http://doc.yc-l.com/#/README 在线演示地址:http://yc.yc-l.com/#/login 源码github:https://github.com/linb ...

  7. C#动态构建表达式树(三)——表达式的组合

    C#动态构建表达式树(三)--表达式的组合 前言 在筛选数据的过程中,可能会有这样的情况:有一些查询条件是公共的,但是根据具体的传入参数可能需要再额外增加一个条件.对于这种问题一般有两种方法: a. ...

  8. HCNP Routing&Switching之路由控制、路由策略和IP-Prefix List

    前文我们了解了IS-IS路由聚合和认证相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/15306645.html:今天我们来聊一聊路由控制技术中的路由策 ...

  9. react 的一些学习资料

    * react开发实战 (Pro React) https://github.com/apress/pro-react * react 配置好的环境https://github.com/bricksp ...

  10. javascript 继承 inheritance prototype

      * Rectangle继承Shape function Shape() { this.x = 0; this.y = 0; } Shape.prototype.move = function(x, ...