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?BiCollectorthat will make your life easier