java - 使用 Collectors.groupingBy 创建复杂对象

标签 java lambda mapreduce java-8

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

但是如果想创建一个 Map<Person.Sex, Averager> 怎么办?使用 lambda + groupingBy,而不是简单的平均值,如教程末尾所示:

Map<Person.Sex, Integer> totalAgeByGender =
    roster
        .stream()
        .collect(
            Collectors.groupingBy(
                Person::getGender,                      
                Collectors.reducing(
                    0,
                    Person::getAge,
                    Integer::sum)));

最佳答案

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

流从 Persons 开始,我们希望 Averagers 作为 map 的值。要从 Person 到 Averager,我们首先需要将每个 Person 映射到他们的年龄(一个整数),然后将这些整数提供给一个 Averager。

我们首先对性别进行分组,因此需要处理与每个性别对应的 Persons。下一步是使用 mapping 收集器作为 groupingBy 的下游收集器将 Persons 映射到他们的年龄。

现在您已经有了年龄,您想要为每个组创建 Averager 实例。本教程中的 Averager 类已经具有收集器方法——它支持适合传递给 Stream.collect 调用的供应商、累加器和组合器函数前面的例子。不过,我们不使用 Stream.collect,而是希望使用 Averager 方法为我们刚刚建立的 mapping 收集器形成一个嵌套的下游收集器。鉴于这些方法,创建 Collector 的便捷方法是使用 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。

本教程定义了用于 Stream.collect 方法的三个 Averager 方法,它具有以下签名(为简洁起见省略了泛型):

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.collectBinaryOperator 兼容需要 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))));

关于java - 使用 Collectors.groupingBy 创建复杂对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22926077/

相关文章:

java - 创建具有相对路径的 URL 对象

java - getformat 用于显示周

python - Lambda、可调用类实例和作用域;为什么它在 Python 2.7 中不起作用?

java - 并行MapReduce

hadoop - 使用MapReduce进行聚类的设计

mapreduce - Spark 日志中的阶段是什么意思?

java - Xpages: managedBean 不断是 "losing"全局 Domino Session 对象

java - 以编程方式监听特定 url 的网络调用

c++ - 如何制作可变参数 lambda?

java - 创建 Lambda 函数实例