Java8流GroupByPOJO
我有一个POJO的集合:Java8流GroupByPOJO,java,java-8,java-stream,collectors,Java,Java 8,Java Stream,Collectors,我有一个POJO的集合: public class Foo { String name; String date; int count; } 我需要按名称和总和计数遍历集合、groupbyfoos,然后使用具有总和计数的pojo创建新集合 我现在是这样做的: List<Foo> foosToSum = ... Map<String, List<Foo>> foosGroupedByName = foosToSum.s
public class Foo {
String name;
String date;
int count;
}
我需要按名称和总和计数遍历集合、groupbyfoos,然后使用具有总和计数的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());
List foosToSum=。。。
Map foosGroupedByName=foosToSum.stream()
.collect(Collectors.groupingBy(Foo::getName));
List groupedFoos=foosGroupedByName.keySet().stream().map(名称->{
int totalCount=0;
字符串日期=”;
for(Foo-Foo:foosGroupedByName.get(name)){
totalCount+=foo.getCount();
date=foo.getDate()//使用最后一个
}
返回新的Foo(名称、日期、总数);
}).collect(Collectors.toList());
有没有更美的方法来处理溪流
更新感谢大家的帮助。所有答案都很好。
我决定在pojo中创建合并函数
最终的解决方案如下所示:
Collection<Foo> groupedFoos = foosToSum.stream()
.collect(Collectors.toMap(Foo::getName, Function.identity(), Foo::merge))
.values();
Collection groupedFoos=foosToSum.stream()
.collect(Collectors.toMap(Foo::getName,Function.identity(),Foo::merge))
.values();
是的,您可以在您的分组方式中使用下游收集器来立即对计数求和。之后,流式处理map并将其映射到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());
一个更高效的解决方案可以避免分组到一个映射中,而只是立即流式处理,但会牺牲一些可读性(在我看来):
通过减少Foo而不是int,我们可以记住名称,并可以立即将其相加为Foo。您可以使用groupingBy
或toMap
收集器来实现这一点,因为使用哪一个是有争议的,所以我会让您决定您喜欢哪一个
为了更好的可读性,我将在Foo
中创建一个合并函数,并将所有合并逻辑隐藏在其中
这也意味着更好的可维护性,因为合并越复杂,您只需更改一个位置,即merge
方法,而不是流查询
e、 g
现在您可以执行以下操作:
Collection<Foo> resultSet = foosToSum.stream()
.collect(Collectors.toMap(Foo::getName,
Function.identity(), Foo::merge)).values();
此外,如果出于某种原因明确要求使用列表
而不是集合
,则可以使用ArrayList
复制构造函数来完成
List<Foo> resultList = new ArrayList<>(resultSet);
List resultList=newarraylist(resultSet);
更新
正如@Federico在评论中提到的,上面的最后一个合并函数非常昂贵,因为它创建了可以避免的不必要的对象。因此,正如他所建议的,一个更友好的替代方法是继续使用我上面展示的第一个合并函数,然后将您的流查询更改为:
Collection<Foo> resultSet = foosToSum.stream()
.collect(Collectors.toMap(Foo::getName,
f -> new Foo(f.getName(), null, f.getCount()), Foo::merge))
.values();
Collection resultSet=foosToSum.stream()
.collect(Collectors.toMap)(Foo::getName,
f->new Foo(f.getName(),null,f.getCount()),Foo::merge))
.values();
很好的解决方案。但是,我需要以某种方式从名为Foos的分组中获取最新日期字段。我的错误是,我已经写过了,这并不重要,但确实重要。好吧,您可以尝试使用foo2.getDate()
而不是null
。但是你要依赖的是仍然在订购的食物。如果您想确定,可以使用类似于getLater(foo1.getDate(),foo2.getDate())
的东西。正如Aominè在另一个答案中所说的,您可以将所有的逻辑放在Foo类中并编写…reduce(new Foo(),Foo::merge)
Hi Aominè!只是一个评论。。。如果使用Function.identity()
作为值映射器函数,则源集合的元素发生变化是正确的。因此,您建议在合并时创建新的Foo
元素。虽然这是正确的,但它会创建不必要的对象。您可以在值映射器函数中创建一个副本(只需使用一个副本构造函数),然后在此副本上进行合并,就像您原来的merge
方法一样。您好,@FedericoPeraltaSchaffner我刚才看到了您的评论,但无法回复。现在我是:)。无论如何,你是对的,我已经相应地更新了我的答案。谢谢你的反馈。嘿,奥米内太好了,你已经更新了你已经接受的答案;)我已经提前投票:)@FedericoPeraltaSchaffner事实上,再看看我的代码,它只在出现键冲突时创建了新对象,在这种情况下,没有冲突,它仍然会从源代码维护特定对象。所以你不仅改进了我的代码,还解决了一个微妙的问题:)。奥米内,是的,你在这方面也是正确的。我没有注意到。那好吧。我们一石二鸟。。。
public Foo merge(Foo another){
return new Foo(this.getName(), null, this.getCount() + another.getCount());
}
List<Foo> resultList = new ArrayList<>(resultSet);
Collection<Foo> resultSet = foosToSum.stream()
.collect(Collectors.toMap(Foo::getName,
f -> new Foo(f.getName(), null, f.getCount()), Foo::merge))
.values();