If you have a special, dedicated constructor and hashCode
and equals
methods consistently implemented in Foo
as follows:
public Foo(Foo that) { // not a copy constructor!!!
this.category = that.category;
this.amount = 0;
this.price = 0;
}
public int hashCode() {
return Objects.hashCode(category);
}
public boolean equals(Object another) {
if (another == this) return true;
if (!(another instanceof Foo)) return false;
Foo that = (Foo) another;
return Objects.equals(this.category, that.category);
}
The hashCode
and equals
implementations above allow you to use Foo
as a meaningful key in the map (otherwise your map would be broken).
Now, with the help of a new method in Foo
that performs the aggregation of the amount
and price
attributes at the same time, you can do what you want in 2 steps. First the method:
public void aggregate(Foo that) {
this.amount += that.amount;
this.price += that.price;
}
Now the final solution:
Map<Foo, List<Foo>> result = fooList.stream().collect(
Collectors.collectingAndThen(
Collectors.groupingBy(Foo::new), // works: special ctor, hashCode & equals
m -> { m.forEach((k, v) -> v.forEach(k::aggregate)); return m; }));
EDIT: a few observations were missing...
On one hand, this solution forces you to use an implementation of hashCode
and equals
that considers two different Foo
instances as equal if they belong to the same category
. Maybe this is not desired, or you already have an implementation that takes more or other attributes into account.
On the other hand, using Foo
as the key of a map which is used to group instances by one of its attributes is quite an uncommon use case. I think it would be better to just use the category
attribute to group by category and have two maps: Map<String, List<Foo>>
to keep the groups and Map<String, Foo>
to keep the aggregated price
and amount
, with the key being the category
in both cases.
Besides this, this solution mutates the keys of the map after the entries are put into it. This is dangerous, because this could break the map. However, here I'm only mutating attributes of Foo
that don't participate in neither hashCode
nor equals
Foo
's implementation. I think that this risk is acceptable in this case, due to the unusuality of the requirement.
Collectors.summarizingLong
? – MottaBiCollector
that will make your life easier – Bacciform