0
点赞
收藏
分享

微信扫一扫

深入介绍和使用Java8的Collector接口

洲行 2022-01-24 阅读 49

深入介绍和使用Java8的Collector接口

首先看这篇文章需要有一点Stream()的知识,lambda是最基本的使用规则,因为我们全文都在使用函数式接口

一 .Collector

在这里插入图片描述
不知道你看见这种代码眼熟吗,先通过将每个元素map处理后,直接一个collect方法就能拿到我们想要收集起来的类型,里面跟着一起写奇奇怪怪的方法,我们来看一下collect里面包含什么

在这里插入图片描述

一个Collector类型的对象,我们继续点进去看

在这里插入图片描述
Collector接口里面有五个具体的方法,其中四个为函数式接口对象

函数式接口可以被隐式转换为 lambda 表达式

在这里插入图片描述
添加了函数式接口注解表明BiConsumer为一个函数式接口,可以看到accept方法传入两个T,U类型的参数,没有返回值,可以说明这是一个消费形的接口,只是处理出入参数 类似于隐式(a,b)->{ System.out.printf(a,b);}

我们来看看这我一个方法具体做了哪些

  • supplier参数用于生成结果容器,容器类型为A
  • accumulator用于消费元素,也就是归纳元素,这里的T就是元素,它会将流中的元素一个一个与结果容器A发生操作
  • combiner用于两个两个合并并行执行的线程的执行结果,将其合并为一个最终结果A
  • finisher用于将之前整合完的结果R转换成为A
  • characteristics表示当前Collector的特征值,这是个不可变Set

基础对象

@Data
    @Builder
    static class ObiectMap{
        private String key;
        private String value;
    }
    @Data
    @Builder
    static class NumObj{
        private String key;
        private Integer value;
    }

在这里插入图片描述
我们直接实现一下这个接口,首先我们将list里面的数据收集起来作为对象,然后我们想把相同key的元素累加后收集起来起来Supplier我们提供一个HashMap,accumulator返回一个消费型接口,我们可以直接拿到第一步收集起来的容器,然后判断相同的key的时候累加起来value,combiner代表为并行收集后将并行数据处理,finisher将最后收集的元素类型转换在这里插入图片描述

在这里插入图片描述
通过这些方法,我们可以拿到我们想要收集起来的任何类型,因为具体收集处理方法是你传入的,以前我们的接口只能传入参数对象,而引入函数式接口,我们可以直接将方法的实现传入,大大增加了我们方法的复用性

二. Collectors

很多场景下,我们并不需要像自己手写一些收集方法,很麻烦也不能复用,所以我们可以使用内置的Collectors工具类取实现Collector接口
在这里插入图片描述
返回类型都是 Collector 接口对象,各个方法用法如下

  1. toCollection,toList,toSet:处理并将结果返回成集合对象
  2. toMap: 处理并收集成Map
  3. joining:处理并将结果返回成字符串
  4. mapping:首先将流中的元素从 T 转成 U,然后再将含有 U 的流转给 downstream 继续处理
  5. collectingAndThen:首先将流传给 downstream 处理,然后将 downstream 中的 R 转成 RR,最后的结果为 RR
  6. counting:求流中元素的总数,类型为 Long
  7. minBy: 求流中最小的元素
  8. maxBy:求流中最大的元素
  9. summingInt,summingLong,summingDouble:求流中元素之和,和的类型分别是 int, long, double 类型
  10. averagingInt,averagingLong,averagingDouble:求流中元素的平均数,平均数的类型分别是 int, long, double 类型
  11. reducing:减少流中的元素
  12. groupingBy:将流中的元素分组处理
  13. groupingByConcurrent:以并行的方式将流中的元素分组处理
  14. partitioningBy:将流中的元素分区处理
  15. toMap, toConcurrentMap:处理并将结果返回成 Map 对象
  16. summarizingInt,summarizingLong,summarizingDouble:汇总流中的元素,汇总后的类型分别是 IntSummaryStatistics, LongSummaryStatistics, DoubleSummaryStatistics

2.1 toCollection 、toList、toSet

在这里插入图片描述在这里插入图片描述

传入一个Supplier容器,accumulator作用的是将Collection元素一个个加到容器中,combiner加入所有元素

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这样看toList,toSet一下子就看的很明白了,List,Set也是继承了Collection接口,只不过是容器类型不一样,Supplier传入各自类型,accumulator传入各自累加方法,combiner传入并行处理方法

区分只是并行收集的SET类型传参不一样,具体如下

  • CONCURRENT 如果使用 parallelStream,characteristics 方法返回的 set 集合中可以加上这个枚举
  • UNORDERED 如果流中的数据是没有顺序的,也可以加上这个枚举
  • IDENTITY_FINISH加上这个表示 finisher 方法中的参数和返回值完全一致,finisher 方法不会执行了

2.2 toMap

我们再来看一下toMap方法
在这里插入图片描述
传入两个Function对应Map的Key,Value,传入你自己想要的key和value即可,还传入一个mergeFunction,这个BinaryOperator接口对象继承了BiFunction,传入两个参数,返回一个参数,这个是为了防止冲突时的解决方案,我们进去merge方法去看
在这里插入图片描述
调用的是Map里的merge方法,处理冲突第一步,根据传入的key先拿到旧值,如果旧值为空就用新值替代,如果不为空就代表有冲突,这时候我们调用外部的merge方法解决冲突,返回一个解决后的值,如果此时value还为null,那就remove掉key,否则就put进map
在这里插入图片描述

此时继续调用底部方法,accumulator实现为上面解决冲突的方法,combiner也解决并行收集的冲突问题

2.3 joining

在这里插入图片描述
首先,joining方法返回可以按照我们想要的类型的字符串输出

每一个元素类型T CharSequence, 容器A StringBuilder, 返回R 类型String
首先创建一个StringBuilder容器,里面每个元素都append进去,并行收集时全部append,最终收集为String类型

2.4 mapping

类似于先拿到特定的值,与之传入的downstream发生关系
在这里插入图片描述
我们可以看到五个参数四个都是用的传入downstream的参数,只有中间accumulator将元素塞入容器的操作,用了特定的取值方法取值然后再作用于容器中

举个例子,我们想将根据key分组的valueList的值收集起来,不直接拿到对象list
在这里插入图片描述
在这里插入图片描述

2.5 collectingAndThen

将downstreamR元素拿到后转换成RR类型
在这里插入图片描述
在这里插入图片描述

finisher用的也是Function接口,我们直接调用andThen,可以看到先调用自己的apply方法后,调用传入finisher的apply方法

2.6 counting、reducing、maxBy、minBy

底层都是运用规约reducing接口,用于将一个一个元素相互作用后产生结果

在这里插入图片描述
identity 基准值
mapper 映射函数
op 操作函数

boxSupplier将基准值传入返回一个跟基准值类型的数组(并添加自己进入元素),accumulator对应元素与容器作用reduce会将所有值与容器中第一个元素作用,combiner并行会将所有并行数组第一个元素加到一起,finisher返回数组第一个元素

在这里插入图片描述
底层还是调用reducing接口
在这里插入图片描述

在这里插入图片描述
有可能传入空对象,所以会存在Optional类型的,BinaryOperator.maxBy(comparator)传入比较器,收集成OptionalBox类型的容器,调用外部比较器进行比较然后放到容器中,并行也进行比较,最终返回一个Optional类型的对象

minBy同理
在这里插入图片描述

counting
在这里插入图片描述

既然我们知道了底层reducing做的什么事情,那我们直接来看看counting,首先给了个初始基准值,防止出现Optional类型,也是我们容器的类型,然后将每个元素映射成1L,与数组中的基准值进行相加操作,返回一个最终相加的值,就是我们的统计数量

2.7 summingInt,summingLong,summingDouble

在这里插入图片描述
这几个接口就很容易理解了,初始容器值设置为int数组包含一个0,将数组中元素与applyAsInt元素相加,applyAsInt拿到对象的int值,最终返回相加后的元素,就是所有相加的值

2.8 averagingInt,averagingLong,averagingDouble

在这里插入图片描述
看起来更简单了,初始化long数组长度为2,第一个元素记录总值,第二元素个记录数量,最终返回一个总值/总数的平均值

2.9 groupingBy groupingByConcurrent

分组接口,我们最常用的应该就是这个了
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果你只传入分组的key,默认我们分组后的数据类型就是 Map<key,List(Object)>

在这里插入图片描述
搞清楚groupingBy对我们来说也是至关重要的

groupingByConcurrent用了ConcurrentHashMap解决并发收集线程问题
在这里插入图片描述

2.10 summarizingInt,summarizingLong,summarizingDouble:汇总流中的元素,汇总后的类型分别是 IntSummaryStatistics, LongSummaryStatistics, DoubleSummaryStatistics

Collectors.summarizingInt 汇总所有信息包括数量、求和、平均值、最小值、最大值

在这里插入图片描述

三 举个例子

基础对象

	@Data
    @Builder
    static class ObjectMap{
        private String key;
        private String value;
    }
    @Data
    @Builder
    static class NumObj{
        private String key;
        private Integer value;
    }

一组数据

List<String> strings = Lists.newArrayList("name=kk", "sex=1", "tel=1111", "email=xx@qq.com","name=ww","name=yy");

3.1 通过ObjectMap对象的key进行分组,将value收集起来成list

 		System.out.println("map1: ");
        Map<String, List<String>> collect1 = strings.stream().map(e -> { //封装成对象
            String[] split = e.split("\\=", 2);
            return ObjectMap.builder().key(split[0]).value(split[1]).build();
        }).collect(Collectors.toMap(ObjectMap::getKey,v->Lists.newArrayList(v.getValue()), (List<String> newList, List<String> oldList) -> { // Collectors.toMap(a,b,(n1,n2)) -> a代表Map的key (这里直接用方法引用拿到key) b代表value (n1,n2)代表key相同时value的处理办法,直接合并List
            oldList.addAll(newList);
            return oldList;
        }));
        collect1.forEach((k,v)->{
            System.out.printf(k+" { ");
            String vList = v.stream().collect(Collectors.joining(",")); //加上逗号,最后一个不加
            System.out.printf(vList);
            System.out.printf(" }");
            System.out.println();
        });
        System.out.println();

在这里插入图片描述

3.2 通过ObjectMap对象的key进行分组,将value收集起来成list 2.0版本

 		System.out.println("map2: ");
        Map<String, List<String>> collect2 = strings.stream().map(e -> {
            String[] split = e.split("\\=", 2);
            return ObjectMap.builder().key(split[0]).value(split[1]).build();
        }).collect(Collectors.groupingBy(ObjectMap::getKey, Collectors.mapping(ObjectMap::getValue, Collectors.toList()))); //Collectors.groupingBy(a,b) a还是通过key来分组 ,b将value收集起来做list value
        collect2.forEach((k,v)->{
            System.out.printf(k+" { ");
            String vList = v.stream().collect(Collectors.joining(",")); //加上逗号,最后一个不加
            System.out.printf(vList);
            System.out.printf(" }");
            System.out.println();
        });
        System.out.println();

在这里插入图片描述

3.3 通过ObjectMap对象的key进行分组,value为分组数量

 		System.out.println("map4: get name with count");
        Map<String, Long> collect4 = strings.stream().map(e -> {
            String[] split = e.split("\\=", 2);
            return ObjectMap.builder().key(split[0]).value(split[1]).build();
        }).collect(Collectors.groupingBy(ObjectMap::getKey, Collectors.counting())); // 拿到数量
        collect4.forEach((k,v)->{
            System.out.printf(k+" { ");
            System.out.printf("%d",v);
            System.out.printf(" }");
            System.out.println();
        });
        System.out.println();

在这里插入图片描述
一组数据

List<String> specialString = Lists.newArrayList( "name=cc","sex=dd","name=bb","name=aa", "sex=aa","name=ww");

3.4 将相同key的value集合起来(对象list或者list),并按照指定字符串进行内部排序 例如aa > bb > cc (指定字符) 其他的排在后边

		   Map<String,Integer> keyMap=new HashMap(){{
           put("aa",1);
           put("bb",2);
           put("cc",3);
        }};
        Map<String, List<String>> collect5 = specialString.stream().map(e -> { //封装成对象
            String[] split = e.split("\\=", 2);
            return ObjectMap.builder().key(split[0]).value(split[1]).build();
        }).collect(Collectors.groupingBy(ObjectMap::getKey, Collectors.mapping(ObjectMap::getValue, toSortedList((o1, o2) -> { //groupingBy 可以有三个重载方法 三个参数分别是 1.分类器(通过什么方式分类) 2.封装类(默认hashmap 可以传一些其他Collector) 3.收集dowStream (默认tolist)
            if (keyMap.get(o1) == null) { //Collectors.mapping 返回是一个Collector 里面可以指定通过特定值收集  toSortedList我们自定义一个收集器
                return 1;
            } else if (keyMap.get(o2) == null) {
                return -1;
            } else if (keyMap.get(o1) == null && keyMap.get(o2) == null) {
                return 0;
            }
            return keyMap.get(o1).compareTo(keyMap.get(o2));
        }))));

在这里插入图片描述

一组数据

List<String> numStr = Lists.newArrayList("aa=1", "bb=10", "cc=1111", "aa=66","cc=22","dd=101");

3.5 通过ObjectMap对象的key进行分组,获取每组最大的元素

	    System.out.println("map8: get sorted max element");
        List<String> numStr = Lists.newArrayList("aa=1", "bb=10", "cc=1111", "aa=66","cc=22","dd=101");
        Map<String, Integer> collect8 = numStr.stream().map(e -> {
            String[] split = e.split("=");
            return NumObj.builder().key(split[0]).value(Integer.parseInt(split[1])).build(); //老样子先转换成对象
        }).collect(Collectors.groupingBy(NumObj::getKey // 然后通过groupingBy使用对象的KEY去分类 reducing(a,b,c)规约函数流由一个个元素组成,归约就是将一个个元素“折叠”成一个值,如求和、求最值、求平均值都是归约操作。
                , Collectors.collectingAndThen(Collectors.reducing(0, NumObj::getValue, (c1, c2) -> { //collectingAndThen(a,b) 包装一层,不然外边拿到的是Optional类对象,
                     return c1>c2?c1:c2;
                }), e -> e)));
        collect8.forEach((k,v)->{
            System.out.println(k+":"+v);
        });

在这里插入图片描述

制作不易,转载请标注~~

举报

相关推荐

0 条评论