java8学习之自定义收集器实现
在上次花了几个篇幅对Collector收集器的javadoc进行了详细的解读,其涉及到的文章有:
- http://www.cnblogs.com/webor2006/p/8311074.html
- http://www.cnblogs.com/webor2006/p/8318066.html
- http://www.cnblogs.com/webor2006/p/8324390.html
而系统有一个对它的具体实现则是在Collectors类中有一个CollectorImpl:

为了加深对Collector收集器的理解,咱们这次自定义一个自己的收集器,而不采用系统写好的,在自定义之前,先来回顾一下收集器的一些要点:
1、泛型参数的意义:

2、核心方法的意义:
其中包含五个重要的方法,具体如下:

其中对于Characteristics这个枚举里面的值再说明一下,之后会用代码来进行进一步说明:




好了~~复习了这些细节之后,下面就开始编码,咱们对Set数据进行收集,如下:

接着来定义三个泛型参数,下面一个个来定义,首先是流中元素的类型,咱们用T来表示,如下:

第二个参数则是中间累加的结果容器,很显然是Set<T>,如下:

最后一个参数则是最终结果的类型,咱们最终结果类型跟中间结果容器类型是一样的,都是Set,所以:

很显然目前的T还是标红的,是因为咱们还得在它上面定义一下,如下:

接下来将接口中的方法都得实现一下,如下:


接下来一个个方法具体实现:
supplier():
首先先在这方法中打印一下日志,待之后看一下整个调用过程:

那接下来就是看如何实现呢?它的作用是生成一个存放中间结果的空的容器,看下它的返回值是:Supplier<Set<T>>,不传参数,返回一个Set<T>,这里咱们用HashSet,那可以采用方法引用,如下:

accumulator():
还是先打印日志便于运行观察:

该方法的主要作用是进行中间结果的不断累加,看一下它的返回类型BiConsumer<Set<T>, T>,接收两个参数,不返回值,注意一下这两个参数的顺序:第一个参数为不断增加的结果容器,第二个参数流中遍历的下一个元素,当然就是不断将第二个参数往第一个参数中累加,所以下面先用Lambda表达式的方式来实现一下:

这时还是可以用方法引用:

对于这个方法引用,其第一个参数是调用累加的那个中间结果容器,而第二个参数则是遍历的下一个元素。
那如果将这个Set换成HashSet呢?


咱们先看替换完之后能不能编译:

IDE提示貌似也没道出所有然来,很是奇怪,既然HashSet是Set的直接实现类,为啥就不行了呢?其实原因是这样:
假如这个HashSet替换完了是允许的,那假如咱们在supplier()函数中将HashSet改为TreeSet呢?那明显accumulator()方法的具体实现也得进行修改,而如果是面向接口那改了supplier()的具体实现完全不影响accumulator()的实现,所以说这个实现需要注意。
combiner():
同样先打印日志:

而此方法的作用就是将两个部分结果进行合并,所以可以这样实现:

finisher():
首先也打印日志:

它的作用其实就是将中间累加的结果容器转换成最终的结果,而对于咱们这边的场景其最终结果类型也就是中间结果类型,所以直接将累加的中间结果容器返回既可,如下:

插个小细节,还可以用Function提供的一个静态方式来取而代之,之前咱们也介绍过:

所以:

characteristics():
同样先增加日志:

它主要是决定收集器的一些特性的,那这里返回一个无序特性,如下:

那这里面实现中涉及到的代码其实是参照Collectors类中的,如下:

至此,咱们自定义的收集器就已经定义好啦,接下来咱们来使用一下它,将定义的List转换成Set,具体如下:

看一下此时它返回的数据类型:

所以咱们用它来接收一下,并打印出结果:

如果再增加一个重复的元素当然会被过滤掉,因为Set是会去重滴:

从日志打印的记录中发现,居然finisher()方法木要有被调用,这也是在开篇回顾Collector重要方法时提到了,它有时能调用,有时是不会调用的,当中间的结果容器的类型跟最终结果的类型是一致的话,其实该方法直接抛个异常就行了,不用实现,而咱们写的这种情况刚好就属于这种:

关于最终打印的日志输出,它的执行流程为啥是这样的呢?下面从stream.collect()的源码中找寻答案,如下:


接下来只分析关键代码,因为主要是为了寻找为啥最终的输出顺序是这样的问题,先来看一下该方法的实现:





接着继续回到ReduceOps.makeRef()方法往下看:


所以这三句话的调用就是在初始化时被调用的,但是!!!有一点需要注意,只是回调函数调用了,但是函数式接口并没有调用,如何理解,比如说:

如果BinaryOperator函数式接口被调用的话,其Lambda表达式肯定会被执行,最终执行合并操作,这里说的函数式接口没调用是指并非真正的去执行了合并操作了,其实目前只是获取了函数式接口的实例而已,咱们再来体会下:

接着再来分析一下为啥这个被打印了两次,如下:

其实是在下面两处被调用的:

接着得回到它的上一级调用来看:

另外为啥finisher()方法木有被调用呢?其实也就是在刚才分析的代码处就可以明白了:


所以通过这个实现就能解释为啥咱们的finisher()方法木有掉用啦,原因就是由于咱们给收集器增加了如下特性:

那接下来做个实验,将这个特性去掉,看是否这次能执行finisher()?

基于此咱们再来看一下系统收集器实现中的一个细节:

看一下它的具体实现,豁然开朗:

实际开发中可能自定义收集器的场景比较少,但是!!如果你研究清楚了如何自己来写一个收集器,那可以帮助我们更加自信的应用收集器的任何东东,也就是有了底层的支持才能走出咱们的自信,所以学会自定义还是非常有意义的~
java8学习之自定义收集器实现的更多相关文章
- java8学习之自定义收集器深度剖析与并行流陷阱
自定义收集器深度剖析: 在上次[http://www.cnblogs.com/webor2006/p/8342427.html]中咱们自定义了一个收集器,这对如何使用收集器Collector是极有帮助 ...
- Java8学习笔记(十)--自定义收集器
前言 以前写过Java8中的自定义收集器,当时只是在文章末尾放了个例子,觉得基本用法挺简单,而且有些东西没搞懂(比如combiner方法到底做什么的),没有专门写,过了一段时间又忘了,所以,即使还是没 ...
- Java8中重要的收集器Collector
Collector介绍 Java8的stream api能很方便我们对数据进行统计分类等工作,函数式编程的风格让我们方便并且直观地编写统计代码. 例如: Stream<Integer> s ...
- Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论
Java虚拟机JVM学习06 自定义类加载器 父委托机制和命名空间的再讨论 创建用户自定义的类加载器 要创建用户自定义的类加载器,只需要扩展java.lang.ClassLoader类,然后覆盖它的f ...
- java类加载器学习2——自定义类加载器和父类委托机制带来的问题
一.自定义类加载器的一般步骤 Java的类加载器自从JDK1.2开始便引入了一条机制叫做父类委托机制.一个类需要被加载的时候,JVM先会调用他的父类加载器进行加载,父类调用父类的父类,一直到顶级类加载 ...
- Struts学习之自定义拦截器
* 所有的拦截器都需要实现Interceptor接口或者继承Interceptor接口的扩展实现类 * 要重写init().intercept().destroy()方法 * in ...
- Struts2重新学习之自定义拦截器(判断用户是否是登录状态)
拦截器 一:1:概念:Interceptor拦截器类似于我们学习过的过滤器,是可以再action执行前后执行的代码.是web开发时,常用的技术.比如,权限控制,日志记录. 2:多个拦截器Interce ...
- struts2学习(6)自定义拦截器-登录验证拦截器
需求:对登录进行验证,用户名cy 密码123456才能登录进去: 登录进去后,将用户存在session中: 其他链接要来访问(除了登录链接),首先验证是否登录,对这个进行拦截: com.cy.mod ...
- 学习JVM--垃圾回收(二)GC收集器
1. 前言 在上一篇文章中,介绍了JVM中垃圾回收的原理和算法.介绍了通过引用计数和对象可达性分析的算法来筛选出已经没有使用的对象,然后介绍了垃圾收集器中使用的三种收集算法:标记-清除.标记-整理.标 ...
随机推荐
- ACO 蚁群算法(算法流程,TSP例子解析)
算法 计算机 超级计算 高性能 科学探索 1. 算法背景——蚁群的自组织行为特征 高度结构化的组织——虽然蚂蚁的个体行为极其简单,但由个体组成的蚁群却构成高度结构化的社会组织,蚂蚁社会的成员有分工,有 ...
- Returning array from function in C
以下为了通俗易懂,使用意译. I've here very intersting discussion about the best and common ways to return an arra ...
- C基础知识(9):输入输出、文件读写
输入输出 (1) getchar() & putchar() 函数: 读写字符. (2) scanf() 和 printf() 函数:根据提供的format来转换输入为其他数据类型,并根据提供 ...
- C++学习笔记-引用
引用是C语言中没有,而在C++中又很重要的一个概念,通过应用,可以得到变量本身,相对于得到变量的值而言,有更大的操作空间. 普通引用 变量的本质 变量名实质上是一段连续存储空间的别名,是一个标号 程序 ...
- elasticsearch的备份和恢复(转)
vim /etc/elasticsearch/elasticsearch.yml path.repo: ["/data/backups/es_backup"] #备份目录,根据自己 ...
- 编译FFMPEG错误对策
在MINGW32下编译ffmpeg-2.1.1.tar.bz2 step1: ./configure --prefix=/home/Administrator/install --extra-cf ...
- [转帖]docker清理日志
docker清理日志 2017年05月03日 10:37:27 不想当码农的程序员 阅读数 12827 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn. ...
- 什么是云服务器ECS
云服务器(Elastic Compute Service,简称ECS)是阿里云提供的性能卓越.稳定可靠.弹性扩展的IaaS(Infrastructure as a Service)级别云计算服务.云服 ...
- 检测Python程序本身是否已经在运行
为runner.py实现一个函数,检测是否有其他的runner.py进程在正在执行? 除主要用到os模块,还用到了第三方模块psutil
- CentOS 7 防火墙常用操作及常见问题处理
一.常用操作 1.启动防火墙: systemctl start firewalld.service 2.关闭防火墙: systemctl stop firewalld.service 3.添加放行端口 ...