Java 使用收集器创建复杂对象。groupingBy

Java 使用收集器创建复杂对象。groupingBy,java,lambda,mapreduce,java-8,Java,Lambda,Mapreduce,Java 8,在中,可以使用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创建一个映

在中,可以使用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, Integer> totalAgeByGender =
    roster
        .stream()
        .collect(
            Collectors.groupingBy(
                Person::getGender,                      
                Collectors.reducing(
                    0,
                    Person::getAge,
                    Integer::sum)));
Map totalAgeByGender=
名册
.stream()
.收集(
收集者分组(
Person::getGender,
还原剂(
0,
人物:getAge,
整数(sum));

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

流以Persons开始,我们希望平均值作为映射的值。要从一个人到一个平均值,我们首先需要将每个人映射到他们的年龄(整数),然后将整数输入到一个平均值

我们从性别分组开始,因此需要处理与每个性别对应的人。下一步是使用
mapping
收集器作为
groupingBy
的下游收集器,将这些人映射到他们的年龄

现在已经有了ages,您需要为每个组创建
Averager
实例。教程中的
Averager
类已经有了收集器方法——它支持supplier、accumulator和combiner函数,这些函数适合传递到前面示例中的
流。collect
调用。但是,我们不想使用
Stream.collect
,而是希望使用
Averager
方法为刚刚建立的
映射
收集器形成一个嵌套的下游收集器。有了这些方法,创建收集器的方便方法是使用
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...)
请注意,
combiner
方法是一个
BiConsumer
。但是,
方法的
收集器定义如下:

of(Supplier supplier, BiConsumer accumulator, BinaryOperator combiner, Collector.Characteristics... characteristics)
(characteristics
参数是varargs,它与我们无关,因此我们可以忽略它。)

这里需要注意的是,
Collector.of
的组合器是一个
BinaryOperator
,而不是一个
BiConsumer
BinaryOperator
版本执行的操作与
BiConsumer
版本完全相同,但它还返回组合结果。为了解决这个问题,我们只需将
combine()
方法更改为返回一个
Averager
,而不是
void
,并添加一个
返回此
语句:

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))));
地图=
花名册
.收集(分组依据)(个人::getGender,
映射(Person::getAge,
Collector.of(Averager::new,Averager::accept,Averager::combine));

本教程将详细介绍如何进行缩减。但是,对于简单的情况,您不需要手动实现所有功能:

Map<Person.Sex, Double> map = roster.stream().collect(
    Collectors.groupingBy(
        Person::getGender,
        Collectors.averagingInt(Person::getAge)));
Map Map=floster.stream().collect(
收集者分组(
Person::getGender,
收集器.averagint(Person::getAge));
会给你一张从性别到平均年龄的地图

但如果您想了解如何通过指定标识、映射器和操作来定义自己的缩减操作,请查看Stuart Marks的答案

Map<Person.Sex, Double> map = roster.stream().collect(
    Collectors.groupingBy(
        Person::getGender,
        Collectors.averagingInt(Person::getAge)));