在the Java tutorials in aggregate operations的以下摘录中,我们将人物的姓名映射到他们的性别。
Map<Person.Sex, List<String>> namesByGender =
roster
.stream()
.collect(
Collectors.groupingBy(
Person::getGender,
Collectors.mapping(
Person::getName,
Collectors.toList())));
我理解收集操作:
1)通过getGender的结果对流中的每个人进行分组
2)将每个Person映射到getName的结果
3)从结果中形成一个列表
4)生成一个Map,其键是Persons的性别,其数据值是Persons的名字。
我的问题是:
1)收藏家以什么顺序行事?
2)它们之间有什么间歇性的类型?
答案 0 :(得分:1)
如果我们查看groupingBy's
来源,我们会看到以下内容:
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);
};
首先。计算密钥调用Person::getGender
。
二。如果key不存在,则创建新的下游容器ArrayList::new
第三。添加从Person::getName
到容器List::add
ArrayList::new
和List::add
如果我们查看CollectorImpl
方法
Collectors.toList
构造函数的参数
答案 1 :(得分:1)
收藏家不“行动”,因此不按顺序行事。它不像一个收集器在使用间歇类型将其传递给另一个数据之前处理所有数据。
您正在与这些工厂组成单个收集器,这些工厂将在传递到Stream.collect
时立即完成整个工作。
正如documentation of the Collector
interface所解释的那样:
收集器由四个函数指定,这四个函数一起工作以将条目累积到可变结果容器中,并可选择对结果执行最终转换。他们是:
- 创建新的结果容器(supplier())
- 将新数据元素合并到结果容器(accumulator())
- 将两个结果容器合并为一个(合并器())
- 对容器执行可选的最终转换(finisher())
因此,toList()
收集器可以像供应商ArrayList::new
一样简单,累加器List::add
和组合器List::addAll
,不需要自定义终结器,这是怎样的它确实在参考实现中实现,但这是一个实现细节,允许其他实现。
然后,Collectors.mapping
使用指定的下游收集器组成一个新的收集器,并通过首先应用指定的映射器函数并传递其结果来装饰它的累加器函数到原来的累加器功能。结果又是一个由四个函数组成的收集器。在收集操作期间,映射器函数将在添加到列表之前应用于每个元素。
最后,Collectors.groupingBy
会做更复杂的构图。供应商将创建一个新地图,通常为HashMap
。累加器将评估您的分组功能并将结果存储到地图中,使用Map.computeIfAbsent
之类的操作,如果密钥是新的,将评估下游收集器的供应商,然后应用下游收集器的累加器函数,它是场景中的组合函数。如果只有一个包含一个键,或者如果两个映射中都存在一个键,则使用下游的组合器,组合器函数将使用map的值结束Map.merge
。
因此,组合收集器的处理包括指定函数的交错处理,而不是一个接一个地处理一个收集器。换句话说,对于顺序执行,操作将等同于
Map<Person.Sex, List<String>> namesByGender = new HashMap<>();
for(Person p: roster)
namesByGender.computeIfAbsent(p.getGender(), k -> new ArrayList()).add(p.getName());