繁体   English   中英

Java 8 stream groupBy pojo

[英]Java 8 stream groupBy pojo

我有一些pojos:

public class Foo {
    String name;
    String date;
    int count;
}

我需要通过名称和总和计数迭代收集,groupBy Foos,然后使用加总计数的pojos创建新的集合。

我现在就是这样做的:

    List<Foo> foosToSum = ...

    Map<String, List<Foo>> foosGroupedByName = foosToSum.stream()
            .collect(Collectors.groupingBy(Foo::getName));

    List<Foo> groupedFoos = foosGroupedByName.keySet().stream().map(name -> {
        int totalCount = 0;
        String date = "";
        for(Foo foo: foosGroupedByName.get(name)) {
            totalCount += foo.getCount();
            date = foo.getDate() //last is used
        }
        return new Foo(name, date, totalCount);
    }).collect(Collectors.toList());

是否有更美的方式来做流?

更新感谢大家的帮助。 所有答案都很棒。 我决定在pojo中创建合并功能。

最终解决方案如下:

Collection<Foo> groupedFoos = foosToSum.stream()
                    .collect(Collectors.toMap(Foo::getName, Function.identity(), Foo::merge))
                    .values();

您可以使用groupingBy或使用toMap collector来完成它,因为使用它是有争议的,所以我会让你决定你喜欢的那个。

为了更好的可读性,我在Foo创建了一个合并函数,并隐藏了里面的所有合并逻辑。

这也意味着更好的可维护性,因为合并越复杂,您只需要更改一个地方 ,这就是merge方法,而不是流查询。

例如

public Foo merge(Foo another){
     this.count += another.getCount();
     /* further merging if needed...*/
     return this;
}

现在你可以这样做:

Collection<Foo> resultSet = foosToSum.stream()
            .collect(Collectors.toMap(Foo::getName,
                    Function.identity(), Foo::merge)).values();

注意,上面的合并函数会改变源集合中的对象,如果相反,你想让它保持不变,那么你可以像这样构造新的Foo

public Foo merge(Foo another){
      return new Foo(this.getName(), null, this.getCount() + another.getCount());
}

此外,如果由于某种原因您明确要求List<Foo>而不是Collection<Foo>那么可以使用ArrayList复制构造函数完成。

List<Foo> resultList = new ArrayList<>(resultSet);

更新

正如@Federico在评论中提到的,上面的最后一个合并函数是昂贵的,因为它创建了可以避免的不必要的对象。 所以,正如他所建议的,更友好的替代方法是继续我上面显示的第一个合并函数,然后将您的流查询更改为:

Collection<Foo> resultSet = foosToSum.stream()
                .collect(Collectors.toMap(Foo::getName,
                        f -> new Foo(f.getName(), null, f.getCount()), Foo::merge))
                .values();

是的,您可以在groupingBy使用下游收集器来立即对计数求和。 之后,流式传输地图并映射到Foos。

foosToSum.stream()
         .collect(Collectors.groupingBy(Foo::getName,
                                        Collectors.summingInt(Foo::getCount)))
         .entrySet()
         .stream()
         .map(entry -> new Foo(entry.getKey(), null, entry.getValue()))
         .collect(Collectors.toList());

一个更有效的解决方案可以避免分组到地图只是为了立即流,但牺牲了一些可读性(在我看来):

foosToSum.stream()
         .collect(Collectors.groupingBy(Foo::getName,
                                        Collectors.reducing(new Foo(),
                                                            (foo1, foo2) -> new Foo(foo1.getName(), null, foo1.getCount() + foo2.getCount()))))
         .values();

通过减少Foos而不是整数,我们记住了这个名字,并且可以立即归入Foo。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM