我有一些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();
答案 0 :(得分:2)
您可以使用groupingBy
或使用toMap
收集器来执行此操作,因为使用哪个是有争议的,所以我会让您决定您喜欢的那个。
为了更好的可读性,我在Foo
中创建了一个合并函数,并隐藏了那里的所有合并逻辑。
这也意味着更好的可维护性,因为合并越复杂,您只需要更改一个地方,这就是merge
方法,而不是流查询。
e.g。
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();
答案 1 :(得分:1)
是的,您可以使用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。