java8 Stream 之collect(Collector)解析2–超级易懂

前言

第一讲在这里:关于collect(),Collector的解析【理解Collector非常重要,否则无法学习收集部分】

  • 本次我们分析 groupingBy 分组,及嵌套分组
  • 下一讲分析 partitioningBy分区

一、groupingBy使用

groupingBy是用来分组的,它是Collectors的静态方法,类似于SQL中的group by,一般如下:


Person p1 = new Person("hehe", 20);
        Person p2 = new Person("wang", 20);
        Person p6 = new Person("hou", 25);
        Person p5 = new Person("hou", 25);
        Person p3 = new Person("mabi", 30);
        Person p4 = new Person("li", 30);
        List<Person> personList = Arrays.asList(p1, p2, p3, p4, p5, p6);
        /*按照age进行分组*/
         Map<Integer, List<Person>> collect = personList.stream().collect(Collectors.groupingBy(Person::getAge));
        /*groupby 嵌套    先根据 age,再根据 name 分组*/
        Map<Integer, Map<String, List<Person>>> collect6 = personList.stream().collect(groupingBy(Person::getAge, groupingBy(Person::getName)));
复制代码

可以发现,它一般生成Map K就是分组的条件, V一般都用List接受,当然可以对List继续分组,实现多层Map。

二、方法参数解析

还是强调:先熟悉Collector的构造才可以理解。

public static <T, K, D, A, M extends Map<K, D>>
    Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
                                  Supplier<M> mapFactory,
                                  Collector<? super T, A, D> downstream) 
复制代码

groupingBy方法需要传入三个参数:

  • 1、classifier,提供 T,返回 K, 这个的做主要作用是:流中元素是T,我们需要根据它得到的K 进行分组。
  • 2、mapFactory,它最终的形态是 M extends Map<K, D> ,比如示例中的Map<Integer, List>

是最终返回的容器, 这个mapFactory 是要替换 Collector中的 Supplier<A>

  • 3、downstream,它是Collector的实现类,T是流中元素,和classifier中的T 对应,A 就是supplier提供的容器, D是finisher 返回的结果容器。

三、代码详解

public static <T, K, D, A, M extends Map<K, D>>
    Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
                                  Supplier<M> mapFactory,
                                  Collector<? super T, A, D> downstream) {
         ->1.获取collector的容器A
        Supplier<A> downstreamSupplier = downstream.supplier();
        ->2.获取collector的累加器accumulator
        BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
        ->3.制造一个新的累加器,这个lambda表达式,最终需要调用它的accept(Map m,T t)
        BiConsumer<Map<K, A>, T> accumulator = (m, t) -> {
        ->3.1 规约的开始,将T转换为K 将来作为map的键
            K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key");
            ->3.2 Map的default方法,key不存在,则生成一个V,其为容器A 
            A container = m.computeIfAbsent(key, k -> downstreamSupplier.get());
            ->3.3 每次的accumulate只是将 map中key对应的V拿出来,增加t
            downstreamAccumulator.accept(container, t);
        };
        ->4.融合,主要是用于并行流,创建了多个容器的时候,需要对多个map进行合并,所以并行流并不一定块
        BinaryOperator<Map<K, A>> merger = Collectors.<K, A, Map<K, A>>mapMerger(downstream.combiner());
        @SuppressWarnings("unchecked")
        ->将mapFactory强转为 中间结果容器,一定会成功的,我们只是在finisher时,改为<K,D>
        Supplier<Map<K, A>> mangledFactory = (Supplier<Map<K, A>>) mapFactory;
        
        ->5.判断IDENTITY_FINISH,如果A==D 那么就没有执行finisher的意义
        if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {
        ->这个地方请仔细体会:我们构造了完全Map<K,A>形式的 supplier、accmulator、combiner
            return new CollectorImpl<>(mangledFactory, accumulator, merger, CH_ID);
        }
        else {
        //6.否则 执行D的强转
            @SuppressWarnings("unchecked")
            Function<A, A> downstreamFinisher = (Function<A, A>) downstream.finisher();
            Function<Map<K, A>, M> finisher = intermediate -> {
                intermediate.replaceAll((k, v) -> downstreamFinisher.apply(v));
                @SuppressWarnings("unchecked")
                M castResult = (M) intermediate;
                return castResult;
            };
            //7、最终都是生产一个collecotr,供collect()调用
            return new CollectorImpl<>(mangledFactory, accumulator, merger, finisher, CH_NOID);
        }
    }
复制代码

四:总结

1、需要体会 groupingBy的嵌套分组。
2、通过代码可以看出,如果并发收集Map,需要进行combiner,效率可能并没有串行流高,甚至当A和D不相同时,会进行一个replaceAll,效率更低,所以希望使用时,根据需要进行并行or串行, A 和 D的类型尽量可以统一,是否使用IDENTITY_FINISH 要心中有数。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享