前言

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. 洛谷P1083 借教室 题解

    题目 [NOIP2012 提高组] 借教室 题解 这道题是几周之前做到的一道题,本来不想讲的,因为这道题也是用到了二分答案的方法,这类题目之前已经发布过两篇题解了.但这道题还运用了差分数组这个思想,所 ...

  2. 文件流转换为url

      /**  * 文件流转换为url  * @param {} data //文件流  */ export function getObjectURL(data) {   var url = null ...

  3. Nginx总结(九)Nginx服务器高性能优化的配置--轻松实现10万并发访问量

    前面讲了如何配置Nginx虚拟主机,如何配置服务日志等很多基础的内容,大家可以去这里看看nginx系列文章:https://www.cnblogs.com/zhangweizhong/category ...

  4. Java环境搭建与HelloWprld—改变世界的第一步

    1. JDK下载 访问oracle官网:http://www.oracle.com 在首页点击Downloads,进入oracle软件下载页. 在下载页面,点击Java. 选择Java (JDK) f ...

  5. pip安装更换国内源

    镜像地址:阿里云 https://mirrors.aliyun.com/pypi/simple/豆瓣http://pypi.douban.com/simple/清华大学 https://pypi.tu ...

  6. 433MHZ SPI模块使用心得

    最近使用了433MHZ的模块进行了一个通讯项目,选用的是SX1208模块,对接了RTOS和Linux两个操作系统,使用心得如下: 1. 首先要拿来datasheet看一遍,通揽一下它的功能.可以得到一 ...

  7. 浏览器缓存旧的js文件或css文件导致没出现预期效果

    最好在加载的js或css文件后加上 ?v=1.0.0 版本号,更新js后就更改一下版本号即可

  8. Linux服务器通用安全加固指南

    一.基本系统安全 1.保护引导过程(以Grub引导为例) 在 /etc/inittab 中添加 sp:S:respawn:/sbin/sulogin,以确保当切换到单用户模式时 运行级的配置要求输入  ...

  9. xmind使用技巧

    xmind看似每个人都会使用,但是掌握一些小技巧,能够有效提升工作效率. 多行复制粘贴 在xmind中选中多行,复制然后可以直接粘贴到excel.word当中. 在excel.word选中多行,复制然 ...

  10. php 日期相关的类 DateInterval DateTimeZone DatePeriod

    * DateInterval <?php /** * Created by PhpStorm. * User: Mch * Date: 7/18/18 * Time: 21:30 */ $dat ...