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中垃圾回收的原理和算法.介绍了通过引用计数和对象可达性分析的算法来筛选出已经没有使用的对象,然后介绍了垃圾收集器中使用的三种收集算法:标记-清除.标记-整理.标 ...
随机推荐
- python django项目创建及前期准备(使用pycharm)
一.创建django项目 1.打开pycharm软件 2.点击菜单栏 File-->New Project,弹出如下对话框,如下图设置 二.基本配置 1.静态文件目录配置(用于客户端访问后台服务 ...
- Docker学习笔记 — Docker私有仓库搭建
Docker学习笔记 — Docker私有仓库搭建 目录(?)[-] 环境准备 搭建私有仓库 测试 管理仓库中的镜像 查询 删除 Registry V2 和Mavan的管理一样,Dockers ...
- C# 线程安全集合类
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/kang_xuan/article/de ...
- appium+python等待方式使用
背景: 首先,由于一个网速和手机性能以及服务器反应时间等等的原因,不可能导致我们点击了某一个元素之后,它所要链接的页面马上弹出来可以进行下一步操作.所以我们需要一种等待方式,直至链接的页面跳出来之后再 ...
- 【HANA系列】【第七篇】SAP HANA XS使用Data Services查询CDS实体【一】
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[HANA系列][第七篇]SAP HANA XS ...
- vim编辑器详解(week1_day3)
vi编辑器 作用:编辑文本文件中的内容的工具 命令历史 末行模式中,以:和/开头的命令都有历史纪录,可以首先键入:或/然后按上下箭头来选择某个历史命令. 启动vim 在命令行窗口中 ...
- 【miscellaneous】硬解码与软解码
在显卡技术日益成熟的今天,一些概念我们都不是很清楚了,那么显卡硬件解码功能是什么意思呢?高清硬解和软件有什么不同呢?显卡配置需不需要考虑硬件解码呢?电脑爱好者为您分析. 什么是硬件解码? 显卡硬件解码 ...
- ctype.h头文件
定义了一批C语言字符分类函数(C character classification functions),用于测试字符是否属于特定的字符类别,如字母字符.控制字符等等.既支持单字节(Byte)字符,也 ...
- synchronized(三) 锁的膨胀过程(锁的升级过程)深入剖析
警告⚠️:本文耗时很长,先做好心理准备................哈哈哈 本篇我们讲通过大量实例代码及hotspot源码分析偏向锁(批量重偏向.批量撤销).轻量级锁.重量级锁及锁的膨胀过程(也就是 ...
- linux6 下设置oracle自启动(单实例)
操作系统启动过程中,读取/etc/oratab文件,判断是否有哪些数据库是需要自动启动的(N代表不自动启动,Y代表自动启动) elan:/u01/app/oracle/product/10.2.0:Y ...