I have a java object Record:
public Record(ZonedDateTime day, int ptid, String name, String category, int amount) {
this.day= day;
this.id= id;
this.name = name;
this.category = category;
this.amount = amount;
}
I'm grouping a list of Record
s by their day
, and then creating a new Record
which combines the amount
field and returns a map:
Map<ZonedDateTime, Record> map = tempList.stream().collect(Collectors.groupingBy(Record::getDay,
Collectors.collectingAndThen(
Collectors.reducing((r1, r2) -> new Record(r1.getDay(),Integer.toString(r1.getId),r1.getName(),
r1.getCategory(),r1.getAmount() + r2.getAmount())),
Optional::get)));
I want to group the list by day
AND category
. So if day
and category
are the same, I would like to combine the amount
field in a new Record
like I am already doing. I need to add in another Collectors.groupingBy
clause but the syntax just hasn't been working. I believe the return type would be Map<ZonedDateTime, Map<String, List<Record>>>
. I also then need to convert the returned map into a List
.
I've been trying to go off of this example Group by multiple field names in java 8
You could simplify the whole construct by using Collectors.toMap
:
Map<List<Object>, Record> map = tempList.stream()
.collect(Collectors.toMap(
r -> List.of(r.getDay(), r.getCategory()), // or Arrays.asList
Record::new,
Record::merge));
The trick is to group by a composite key. In this case, we're using a List<Object>
with both Record.day
and Record.category
. (List
implements Object.hashCode
and Object.equals
as required, so it can be safely used as the key of any Map
).
For the reduction to work, we need a copy constructor and a merge
method:
public Record(Record r) {
this(r.day, r.name, r.name, r.category, r.amount);
}
public Record merge(Record r) {
this.amount += r.amount;
return this;
}
Finally, to return the list of records, there's no need to do anything fancier than this:
List<Record> result = new ArrayList<>(map.values());