There is a 2 argument groupingBy method - the second argument is a Collector that is applied to the value (a List) associated to each key.
Let's do it step-by-step.
Assuming import static java.util.stream.Collectors.*;
and that we have the list:
List<Item> list = List.of(
new Item("A", 1, 100, "ABC"),
new Item("B", 2, 200, "ABC"),
new Item("A", 3, 300, "ABC"),
new Item("B", 2, 200, "PQR"),
new Item("B", 2, 200, "PQR"),
new Item("A", 2, 200, "PQR"));
- First step: group by
txnId as already in question:
list.stream().collect(groupingBy(Item::txnId))
this will result in a Map<String,List<Item>>:
{ PQR=[Item[sku=B, quantity=2, amount=200, txnId=PQR],
Item[sku=B, quantity=2, amount=200, txnId=PQR],
Item[sku=A, quantity=2, amount=200, txnId=PQR]
],
ABC=[Item[sku=A, quantity=1, amount=100, txnId=ABC],
Item[sku=B, quantity=2, amount=200, txnId=ABC],
Item[sku=A, quantity=3, amount=300, txnId=ABC]
]
}
- Second step: group each internal list by
sku:
list
.stream()
.collect(groupingBy(Item::txnId,
groupingBy(Item::sku)))
resulting in a Map<String,Map<String,List<Item>>:
{ PQR={ A=[ Item[sku=A, quantity=2, amount=200, txnId=PQR] ],
B=[ Item[sku=B, quantity=2, amount=200, txnId=PQR],
Item[sku=B, quantity=2, amount=200, txnId=PQR] ]
},
ABC={ A=[ Item[sku=A, quantity=1, amount=100, txnId=ABC],
Item[sku=A, quantity=3, amount=300, txnId=ABC] ],
B=[ Item[sku=B, quantity=2, amount=200, txnId=ABC] ]
}
}
- Third step reduce the internal lists, summing the values:
I will assume we have a sum method to sum 2 Items returning a new one, very simplified:
Item {
// fields
// getters and setters
public static Item sum(Item item1, Item item2) {
return new Item(item2.sku, item1.quantity+item2.qantity, item1.amount+item2.amount, item2.txnId);
}
}
this could also be defined as a 1-arg non-static method
Now we can use reducing to add up the elements of the internal list:
list
.stream()
.collect(groupingBy(Item::txnId,
groupingBy(Item::sku,
reducing(new Item(null, 0, 0, null),
Item::sum))))
finally resulting in:
{ PQR={ A=Item[sku=A, quantity=2, amount=200, txnId=PQR],
B=Item[sku=B, quantity=4, amount=400, txnId=PQR]
},
ABC={ A=Item[sku=A, quantity=4, amount=400, txnId=ABC],
B=Item[sku=B, quantity=2, amount=200, txnId=ABC]
}
}
Note1: A Lambda expression may be used instead of the sum method for reducing:
....reducing( new Item(null, 0, 0, null),
(i1,i2) -> new Item(i2.sku,
i1.quantity+i2.quantity,
i1.amount+i2.amount,
i2.txnId) )
Note2: To convert the inner Maps to a List use collectingAndThen with Map::values around the second groupingBy:
... collectingAndThen( groupingBy(Item::sku, ...), Map:values ) ...