java8 Stream 之collect(Collecto

前言

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

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

一、groupingBy使用

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

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码
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的构造才可以理解。

1
2
3
4
java复制代码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 返回的结果容器。

三、代码详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
java复制代码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 要心中有数。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%