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

在上次花了几个篇幅对Collector收集器的javadoc进行了详细的解读,其涉及到的文章有:

而系统有一个对它的具体实现则是在Collectors类中有一个CollectorImpl:

7ea23448c078d18a2a7431ebcb0151bd.png

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

1、泛型参数的意义:

436b5d48fd87e6da2a7447962d7c5711.png

2、核心方法的意义:

其中包含五个重要的方法,具体如下:

adb938ea3b634fc82f2306d90948c872.png

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

67a5cf7689e1980d58ebd05183e6b489.png

ba688c35e158d8e1c47f77aeb25b8064.png

387707deb906a10a70c6feedbf331351.png

2249a44def5dfa2f49ad89b061af1b7b.png

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

def9185d6fdf0fdf74a99a34e174aec9.png

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

4f2dbf65e9020598cb3878dfae6a24d4.png

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

687cb55ab2013bcbfe28b6afc899f8dd.png

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

bef6eaefef65e6c4a4c378d782b62dd4.png

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

7603cc2c24eb1593b6a90e4edc49575d.png

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

174c018bea4dc8c706e5f67bd742fed8.png

e82550c7be40c992d15945b67c619c1a.png

接下来一个个方法具体实现:

supplier():

首先先在这方法中打印一下日志,待之后看一下整个调用过程:

9c62dc74075338ce02ad49323d3d616f.png

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

9db8cf29a5b0202e8f53a03bcf10823f.png

accumulator():

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

2609eb076b9794c55f6c6e9c65ec399d.png

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

86910309326338ff22e7d17bbbfcefd1.png

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

79084c553844a27e62e55aabc23ea39b.png

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

那如果将这个Set换成HashSet呢?

63b77e6f72f7e8683d550a296524fd3d.png

6a13951752eb0825c78ff7d5f41245f4.png

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

73ea6616536710607da51e0be76b315d.png

IDE提示貌似也没道出所有然来,很是奇怪,既然HashSet是Set的直接实现类,为啥就不行了呢?其实原因是这样:

假如这个HashSet替换完了是允许的,那假如咱们在supplier()函数中将HashSet改为TreeSet呢?那明显accumulator()方法的具体实现也得进行修改,而如果是面向接口那改了supplier()的具体实现完全不影响accumulator()的实现,所以说这个实现需要注意。

combiner():

同样先打印日志:

61e937540f5270e543c624edb21cfdc9.png

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

663c36693a581fa6d6cf56055b1c472c.png

finisher():

首先也打印日志:

bd2b46bdbcd16a47c54dbbb0286d0770.png

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

b9d7404655a9294ed2c820e0a040c167.png

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

21f13c9441a7a78c180f098394b59e6b.png

所以:

af53c05376512f10b57776e509a64504.png

characteristics():

同样先增加日志:

0907d66ed3cbd5a7c5a8833a29220f7f.png

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

7a751cf36d2bd450f4d90676eb4f2c17.png

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

0353a7c7d92ddeef444ee54b517796d9.png

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

6d0eaab411d12ebc541ad4791c635db6.png

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

22be36eb0e8f354d6ba6185099a83f9a.png

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

6a38fecbbca89496b1b0b0511a5208f2.png

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

d9558216d5da0c42dffdd8f7b5dcc8be.png

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

2f4a244b479f06c9bffcf7869b69bd82.png

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

43c2143d8c41a66d159d37e8dc2ffc1f.png

4406336175c45e50a52ce52a100cfa81.png

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

bae193c632a25d5ce582b870b1884e97.png

cfc27975dc8f467c653a18ea25590020.png

48436a34ad6b531789c4fdf3947013ff.png

a44aa7cdd37677ec3581009fb10e1f64.png

b8f745857e47987ada93116ceda839fd.png

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

9e222b2f76d51eb1a461da24490d5548.png

f1cbc149a2f75e27e82dfe98fb5893e4.png

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

3aea0b5f7f01c1fc4d497d83dfaf9b53.png

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

49ed0f0fb95efd5eb117bc12513e77c3.png

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

50878b0658d67f3f6f16f91d27ce123c.png

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

b1d8833e5f638779cd1343e5d251c85c.png

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

660edde05d00aa007ec87ef1568eed31.png

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

78262a5e899597516f5a4daae93e530f.png

71e03615f67b0de023306e3600e67cdf.png

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

c74fb43682514c7b7e307937d5448927.png

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

c80df0d6d2f472feb477ce487e2e3209.png

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

aaa8fa5ff2e37eef4debd4cadee1b3f1.png

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

527f5cbd6290f3b7b091c07cf7f5126f.png

实际开发中可能自定义收集器的场景比较少,但是!!如果你研究清楚了如何自己来写一个收集器,那可以帮助我们更加自信的应用收集器的任何东东,也就是有了底层的支持才能走出咱们的自信,所以学会自定义还是非常有意义的~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值