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)));

最佳答案

是的,这有点微妙。要更改映射中的值,您必须更改groupingBy调用的下游收集器。在这种情况下,您必须应用嵌套的下游收集器。

流从“人”开始,我们希望将“平均值”作为地图的值。为了从Person到Averager,我们首先需要将每个Person映射到他们的年龄(一个int),然后将这些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...)

阿克!实际上,一旦您克服了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而不是BiConsumerBinaryOperator版本执行的功能与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))));

08-28 19:46