jdk8-32-groupingBy 源码分析

jdk8 集合的工厂类 Collectors 提供了两个很好用的静态函数。

  • groupingBy
  • partitioningBy

groupingBy 实现类似 Mysql 中分分组功能。返回一个 Map; 而 partitioningBy可以看做是 groupingBy 的特殊形式。我们后面会接着介绍。 使用的是比如统计每个城市人的“姓”,可以这么写

1
Map<City, Set<String>> namesByCity = people.stream().collect(groupingBy(Person::getCity, mapping(Person::getLastName, toSet())));

那么,他到底如何利用 Collector 的各个接口来实现的呢?下面我们将深入 JDK 的 Collectors 的源码中,分析一下他是如何工作的。

groupingBy

1 第一个重载方法, 要简单就别那么多要求,给你个List不错了

分组 groupingBy 有三个重载方法。一个比一复杂。

先看最简单的

1
2
3
4
5
6
7
8
/**
* @param <T> the type of the input elements
* @param <K> the type of the keys
*/
public static <T, K> Collector<T, ?, Map<K, List<T>>>
groupingBy(Function<? super T, ? extends K> classifier) {
return groupingBy(classifier, toList());
}

这里 T 是输入集合的元素类型, K 是最后产生的 Map 的主键key 的类型。

函数需要传入一个 Function 的函数式接口,Function 的中作就是分类器,比如传入的类型是T 是一个 Person类型 , 通过 apply() 方法,返回 Person 的属性 name;因为我们要按照 name 来分组。

所以,如果 name 的类型是 String ; 那么 K 就是 String

故而,这个 groupingBy(function) 的功能就很清晰了,就是通过 function 对传入的 T 类型进行分类。然后调用 toList() 方法,也就是说每个分类的Person 会放进一个 List<Person>

最终返回的类型就是 Map<String, List<Person>>.

而如果你不想返回的是一个List , 希望是一个 Set. 你就要使用第二个重载方法~ 他允许你定义最后的输出函数。

2 第二个重载方法:我要输出Set

看一下函数的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @param <T> the type of the input elements
* @param <K> the type of the keys
* @param <A> the intermediate accumulation type of the downstream collector
* @param <D> the result type of the downstream reduction
* @param classifier a classifier function mapping input elements to keys
* @param downstream a {@code Collector} implementing the downstream reduction
*/
public static <T, K, A, D>
Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream) {
return groupingBy(classifier, HashMap::new, downstream);
}

这个函数,明显比上一个要复杂,可以看到,多了一个 Collectordownstream , 可以自定义输出的集合。上面那个简单版本的函数是是写死的 List

这个函数有四个泛型。

  • T 原始元素类型,比如我们这是 Person
  • K 返回 Map 的 key, 即最终返回的Map 的建的类型。如果你想按照人的名字分类,那T就是 String, 如果按照年龄分类,那就是 Interger
  • A 是 downstream 的 accumulation 的中间结果类型
  • D 是最终输出 Map 的 Value 的类型。比如 List 或者 Set

这个方法会调用第三个重载函数,比第二个函数,多了中间的 HashMap::new; 这个是最终返回的 Map 的类型,这里写死的是 HashMap, 如果你想用其他类型,就需要使用最终的也是最复杂的第三个重载函数。

越是你用起来简单的函数,背后的实现就越来越复杂。

3 第三个重载方法: 能看懂就随便你怎么玩

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
43
/**
* @param <T> the type of the input elements
* @param <K> the type of the keys
* @param <A> the intermediate accumulation type of the downstream collector
* @param <D> the result type of the downstream reduction
* @param <M> the type of the resulting {@code Map}
* @param classifier a classifier function mapping input elements to keys
* @param downstream a {@code Collector} implementing the downstream reduction
* @param mapFactory a function which, when called, produces a new empty
* {@code Map} of the desired type
*/
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) {
Supplier<A> downstreamSupplier = downstream.supplier();
BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
BiConsumer<Map<K, A>, T> accumulator = (m, t) -> {
K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key");
A container = m.computeIfAbsent(key, k -> downstreamSupplier.get());
downstreamAccumulator.accept(container, t);
};
BinaryOperator<Map<K, A>> merger = Collectors.<K, A, Map<K, A>>mapMerger(downstream.combiner());
@SuppressWarnings("unchecked")
Supplier<Map<K, A>> mangledFactory = (Supplier<Map<K, A>>) mapFactory;
if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) {
return new CollectorImpl<>(mangledFactory, accumulator, merger, CH_ID);
}
else {
@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;
};
return new CollectorImpl<>(mangledFactory, accumulator, merger, finisher, CH_NOID);
}
}

这个函数有5个泛型,除去上面说的 TKAD
多了一个 mapFactory 的 Supplier() , 用于得到最终返回的 Map 的类型,第二个重载函数写死的 HashMap,这就可以进行自定义。

这个函数有连个值得注意的点:

  • 所有的Collectors 中函数,如果函数内部还要做集合处理的,都是使用 downstream 这种模式,利用 downstream 的各种已有的的 Supplier Accumulator等,修改来实现自己的功能。比如在 groupingBy 这个函数,一开始就获取了 downstream 的各个组件,组合成自己需要的收集器,进而用于完成分类的行为。(你还可以去看一看 collectingAndThen() 这个函数)

image_1be34fqkpdosrnqo8317uhcnc9.png-228kB

  • groupingBy 函数中有有两处强制类型转换。这里其实很难理解,为何可以直接成功的转换呢?

第一处
image_1be351ek7187m19721kb3vijudj13.png-186.2kB

第二处
image_1be353p451uuhvefd57r2u16ad1g.png-161.3kB

两处其实都是将最终结果泛型 D 转成 中间结果 A
对于第一处, mapFactory 是作为一个 Supplier 出现,而他的目的是提供中间结果类型,而他上面的 accumulator 的中间结果类型,就是K,A, 所以这样的强转是一定可以成功的

image_1be35m758itj1grmlc71nu81rrm1t.png-139.5kB

第二处也是一样的,因为全称都使用了 A,其实 AD 在这里,是等价的。所以转换都可以成功。

你去可以从这么一个角度思考问题:

你最终需要生成 D 类型,为何中间要给自己生成一个不一样的 A 类型添堵呢? 你 4 不 4 sa?

朱老师&敏哥 wechat
有惊喜,朋友🙄
我要拿铁不加糖.