在oracle's reduction tutorial中,可以使用Stream.collect计算流中的平均年龄:
Averager averageCollect = roster.stream()
.filter(p -> p.getGender() == Person.Sex.MALE)
.map(Person::getAge)
.collect(Averager::new, Averager::accept, Averager::combine);
但是如果想要使用lambda + groupingBy创建Map<Person.Sex, Averager>
,而不是简单的平均值,如教程结尾所示:
Map<Person.Sex, Integer> totalAgeByGender =
roster
.stream()
.collect(
Collectors.groupingBy(
Person::getGender,
Collectors.reducing(
0,
Person::getAge,
Integer::sum)));
答案 0 :(得分:9)
是的,这有点微妙。要更改映射中的值,您必须更改groupingBy
调用的下游收集器。在这种情况下,您必须应用嵌套的下游收集器。
流开始于人,我们希望平均值作为地图的值。要从Person到Averager,我们首先需要将每个Person映射到它们的年龄(一个int),然后将整数提供给Averager。
我们首先对性别进行分组,因此需要处理与每个性别相对应的人员。下一步是使用mapping
收集器作为groupingBy
的下游收集器,将人员映射到他们的年龄。
现在你已经有了年龄,你想为每个组创建Averager
个实例。本教程中的Averager
类已经具有收集器方法 - 它支持适用于传递给前面示例中的Stream.collect
调用的供应商,累加器和组合器函数。但是,我们希望使用Stream.collect
方法为我们刚刚建立的Averager
收集器构建嵌套的下游收集器,而不是mapping
。鉴于这些方法,创建收集器的便捷方法是使用Collector.of
。
您可以尝试这样的事情:
Map<Person.Sex, Averager> map =
roster.stream()
.collect(groupingBy(Person::getGender,
mapping(Person::getAge,
Collector.of(Averager::new, Averager::accept, Averager::combine))));
但是等等!这不起作用!你得到一个相当讨厌的编译失败,看起来像这样:
error: no suitable method found for of(Averager::new,Averager::accept,Averager::combine)
Collector.of(Averager::new, Averager::accept, Averager::combine))));
method Collector.<T#1,R#1>of(Supplier<R#1>,BiConsumer<R#1,T#1>,BinaryOperator<R#1>,Characteristics...) is not applicable
(cannot infer type-variable(s) T#1,R#1
(argument mismatch; bad return type in method reference
void cannot be converted to R#1))
method Collector.<T#2,A,R#2>of(Supplier<A>,BiConsumer<A,T#2>,BinaryOperator<A>,Function<A,R#2>,Characteristics...) is not applicable
(cannot infer type-variable(s) T#2,A,R#2
(argument mismatch; bad return type in method reference
void cannot be converted to A))
where T#1,R#1,T#2,A,R#2 are type-variables:
T#1 extends Object declared in method <T#1,R#1>of(Supplier<R#1>,BiConsumer<R#1,T#1>,BinaryOperator<R#1>,Characteristics...)
R#1 extends Object declared in method <T#1,R#1>of(Supplier<R#1>,BiConsumer<R#1,T#1>,BinaryOperator<R#1>,Characteristics...)
T#2 extends Object declared in method <T#2,A,R#2>of(Supplier<A>,BiConsumer<A,T#2>,BinaryOperator<A>,Function<A,R#2>,Characteristics...)
A extends Object declared in method <T#2,A,R#2>of(Supplier<A>,BiConsumer<A,T#2>,BinaryOperator<A>,Function<A,R#2>,Characteristics...)
R#2 extends Object declared in method <T#2,A,R#2>of(Supplier<A>,BiConsumer<A,T#2>,BinaryOperator<A>,Function<A,R#2>,Characteristics...)
的Ack!实际上,一旦你超越了20行错误信息的恐吓因素,冷静下来,并阅读了它想说的内容,它实际上非常清楚编译器试图做什么以及如何它失败了。您还必须非常仔细地查看API。
本教程定义了Averager
方法中使用的三种Stream.collect
方法,它具有以下签名(为简洁起见省略了泛型):
collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner)
请注意,combiner
方法是BiConsumer
。但是,Collector.of
方法定义如下:
of(Supplier supplier, BiConsumer accumulator, BinaryOperator combiner, Collector.Characteristics... characteristics)
(characteristics
参数是varargs,它并不关心我们,所以我们可以省略它。)
这里要注意的是Collector.of
的合并器是BinaryOperator
而不是BiConsumer
。 BinaryOperator
版本与BiConsumer
版本完全相同,但此外,会返回合并后的结果。要解决此问题,我们只需更改combine()
方法即可返回Averager
而不是void
,我们会添加return this
语句:
public Averager combine(Averager other) {
total += other.total;
count += other.count;
return this;
}
请注意,此版本的combine()
方法仍适合作为第三个参数传递给Stream.collect
。 BinaryOperator
兼容,需要BiConsumer
;简单地忽略了返回值。
一旦您将此更改更改为Averager.combine
,此代码(与上述相同)应该有效:
Map<Person.Sex, Averager> map =
roster.stream()
.collect(groupingBy(Person::getGender,
mapping(Person::getAge,
Collector.of(Averager::new, Averager::accept, Averager::combine))));
答案 1 :(得分:1)
本教程将解释减少如何工作的全部细节。但是,对于简单的情况,您不需要手动实现所有内容:
Map<Person.Sex, Double> map = roster.stream().collect(
Collectors.groupingBy(
Person::getGender,
Collectors.averagingInt(Person::getAge)));
会为您提供从性别到平均年龄的地图。
但是,如果您想通过指定身份,映射器和操作来了解如何定义自己的缩减操作,请查看Stuart Marks的答案。