Java 8 流组by pojo

Kir*_*rov 5 java java-8 java-stream collectors

我有一系列 pojo:

public class Foo {
    String name;
    String date;
    int count;
}
Run Code Online (Sandbox Code Playgroud)

我需要按名称和总和计数遍历集合、groupBy Foos,然后使用总和计数的 pojo 创建新集合。

这是我现在的做法:

    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());
Run Code Online (Sandbox Code Playgroud)

有没有更美的方式来处理流?

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

最终的解决方案如下所示:

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

Ous*_* D. 3

您可以使用收集器groupingBy或使用toMap收集器来完成此操作,至于使用哪一个是有争议的,所以我会让您决定您喜欢的一个。

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

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

例如

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

现在你可以这样做:

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

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

public Foo merge(Foo another){
      return new Foo(this.getName(), null, this.getCount() + another.getCount());
}
Run Code Online (Sandbox Code Playgroud)

此外,如果出于某种原因您明确需要 aList<Foo>而不是Collection<Foo>,则可以通过使用复制构造函数来完成ArrayList

List<Foo> resultList = new ArrayList<>(resultSet);
Run Code Online (Sandbox Code Playgroud)

更新

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

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

  • 嗨,奥米内!只是评论...如果您使用 Function.identity() 作为值映射器函数,则源集合的元素发生突变是正确的。因此,您建议在合并时创建新的“Foo”元素。虽然这有效且正确,但它会创建不必要的对象。您可以在值映射器函数中创建一个副本(只需使用复制构造函数),然后合并该副本,就像在原始“merge”方法中一样。 (2认同)