问题
Here is my List fooList
class Foo {
private String name;
private int code;
private int account;
private int time;
private String others;
... constructor, getters & setters
}
e.g.(all the value of account has been set to 1)
new Foo(First, 200, 1, 400, other1),
new Foo(First, 200, 1, 300, other1),
new Foo(First, 201, 1, 10, other1),
new Foo(Second, 400, 1, 20, other2),
new Foo(Second, 400, 1, 40, other2),
new Foo(Third, 100, 1, 200, other3),
new Foo(Third, 101, 1, 900, other3)
I want to transform these values by grouping "name" and "code", accounting the number, and summing the "time", e.g.
new Foo(First, 200, 2, 700, other1),
new Foo(First, 201, 1, 10, other1),
new Foo(Second, 400, 2, 60, other2),
new Foo(Third, 100, 1, 200, other3),
new Foo(Third, 101, 1, 900, other3)
I know that I should use stream like this:
Map<String, List<Foo>> map = fooList.stream().collect(groupingBy(Foo::getName()));
but how can I group them by code then do the accounting and summing job ?
Also, what if I want to calculate the average time? e.g.
new Foo(First, 200, 2, 350, other1),
new Foo(First, 201, 1, 10, other1),
new Foo(Second, 400, 2, 30, other2),
new Foo(Third, 100, 1, 200, other3),
new Foo(Third, 101, 1, 900, other3)
can I use both of summingInt(Foo::getAccount)
and averagingInt(Foo::getTime)
instead ?
回答1:
A workaround could be to deal with grouping
with key as List
and casting while mapping back to object type.
List<Foo> result = fooList.stream()
.collect(Collectors.groupingBy(foo ->
Arrays.asList(foo.getName(), foo.getCode(), foo.getAccount()),
Collectors.summingInt(Foo::getTime)))
.entrySet().stream()
.map(entry -> new Foo((String) entry.getKey().get(0),
(Integer) entry.getKey().get(1),
entry.getValue(),
(Integer) entry.getKey().get(2)))
.collect(Collectors.toList());
Cleaner way would be to expose APIs for merge function and performing a toMap
.
Edit: The simplification with toMap
would look like the following
List<Foo> result = new ArrayList<>(fooList.stream()
.collect(Collectors.toMap(foo -> Arrays.asList(foo.getName(), foo.getCode()),
Function.identity(), Foo::aggregateTime))
.values());
where the aggregateTime
is a static method within Foo
such as this :
static Foo aggregateTime(Foo initial, Foo incoming) {
return new Foo(incoming.getName(), incoming.getCode(),
incoming.getAccount(), initial.getTime() + incoming.getTime());
}
回答2:
You can implement your own collect mechanish that might look like following
var collect = Stream.of(
new Foo(1, 200, 1, 400),
new Foo(1, 200, 1, 300),
new Foo(1, 201, 1, 10),
new Foo(2, 400, 1, 20),
new Foo(2, 400, 1, 40),
new Foo(3, 100, 1, 200),
new Foo(3, 101, 1, 900)
)
.collect(
ArrayList::new,
(BiConsumer<ArrayList<Foo>, Foo>) (foos, foo) -> {
var newFoo = foos
.stream()
.filter(f -> f.name == foo.name && f.account == foo.account)
.findFirst()
.map(f -> new Foo(f.name, f.code, f.account + foo.account, f.time + foo.time))
.orElse(foo);
foos.removeIf(f -> f.name == foo.name && f.account == foo.account);
foos.add(newFoo);
},
(foos, foos2) -> foos.addAll(foos2)
);
来源:https://stackoverflow.com/questions/62286182/java-stream-groupingby-and-sum-multiple-fields