首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用Collectors.groupingBy创建复杂对象

使用Collectors.groupingBy创建复杂对象
EN

Stack Overflow用户
提问于 2014-04-08 01:36:19
回答 2查看 7.2K关注 0票数 3

甲骨文还原教程中,可以使用Stream.collect计算流中的平均年龄:

代码语言:javascript
复制
Averager averageCollect = roster.stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .map(Person::getAge)
    .collect(Averager::new, Averager::accept, Averager::combine);

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

代码语言:javascript
复制
Map<Person.Sex, Integer> totalAgeByGender =
    roster
        .stream()
        .collect(
            Collectors.groupingBy(
                Person::getGender,                      
                Collectors.reducing(
                    0,
                    Person::getAge,
                    Integer::sum)));
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2014-04-08 06:29:54

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

流从Person开始,我们需要平均值作为映射的值。为了从一个人到另一个普通人,我们首先需要将每个人映射到他们的年龄(一个int),然后将这些int提供给一个普通人。

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

现在您已经有了年龄,您希望为每个组创建Averager实例。教程中的Averager类已经有了收集器方法--它支持供应商、累加器和组合器函数,这些函数适合在前面的示例中传递到Stream.collect调用。但是,我们不想使用Stream.collect,而是使用Averager方法为我们刚刚建立的mapping收集器形成一个嵌套的下游收集器。考虑到这些方法,使用Collector.of创建收集器是一种方便的方法。

你可以尝试这样的方法:

代码语言:javascript
复制
Map<Person.Sex, Averager> map =
    roster.stream()
          .collect(groupingBy(Person::getGender,
              mapping(Person::getAge,
                  Collector.of(Averager::new, Averager::accept, Averager::combine))));

但是等等!这不管用!您会遇到一个相当糟糕的编译失败,如下所示:

代码语言:javascript
复制
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方法,该方法具有以下签名(为简洁起见省略泛型):

代码语言:javascript
复制
collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner)

注意,combiner方法是一个BiConsumer。但是,Collector.of方法的定义如下:

代码语言:javascript
复制
of(Supplier supplier, BiConsumer accumulator, BinaryOperator combiner, Collector.Characteristics... characteristics)

( characteristics参数是varargs,它与我们无关,所以我们可以忽略它。)

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

代码语言:javascript
复制
public Averager combine(Averager other) {
    total += other.total;
    count += other.count;
    return this;
}

注意,这个版本的combine()方法仍然适合作为第三个参数传递给Stream.collect。在需要一个BinaryOperator的情况下,BiConsumer是兼容的;返回值被忽略了。

一旦您对Averager.combine做了此更改,这段代码(与上面的代码相同)应该可以工作:

代码语言:javascript
复制
Map<Person.Sex, Averager> map =
    roster.stream()
          .collect(groupingBy(Person::getGender,
              mapping(Person::getAge,
                  Collector.of(Averager::new, Averager::accept, Averager::combine))));
票数 9
EN

Stack Overflow用户

发布于 2014-04-08 10:34:42

本教程将解释减少操作的全部细节。但是,对于简单的情况,您不需要手工实现所有内容:

代码语言:javascript
复制
Map<Person.Sex, Double> map = roster.stream().collect(
    Collectors.groupingBy(
        Person::getGender,
        Collectors.averagingInt(Person::getAge)));

会给你从性别到平均年龄的地图。

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

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/22926077

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档