I have an Order class and a LineItem class like below:
@AllArgsConstructor
@Getter
@ToString
static class Order {
long orderId;
List<LineItem> lineItems;
}
@AllArgsConstructor
@Getter
@ToString
static class LineItem {
String name;
BigDecimal price;
}
and a list of orders, from which I want to get a map Map<String,BigDecimal> totalByItem
where key is name of LineItem and value total price from all orders in the list. For this I want to use Collectors.groupingBy in combination with Collectors.reducing but struggling with the correct syntax. Can someone help?
List<Order> orders = List.of(new Order(1L, List.of(new LineItem("Item-A", BigDecimal.valueOf(1)),
new LineItem("Item-B", BigDecimal.valueOf(2)),
new LineItem("Item-C", BigDecimal.valueOf(3)))),
new Order(2L, List.of(new LineItem("Item-A", BigDecimal.valueOf(1)),
new LineItem("Item-D", BigDecimal.valueOf(4)),
new LineItem("Item-E", BigDecimal.valueOf(5)))),
new Order(3L, List.of(new LineItem("Item-B", BigDecimal.valueOf(2)),
new LineItem("Item-C", BigDecimal.valueOf(3)),
new LineItem("Item-D", BigDecimal.valueOf(4)))));
what to put where there are ??? now?
Map<String,BigDecimal> totalByItem =
orders.stream()
.flatMap(order -> order.getLineItems().stream())
.collect(Collectors.groupingBy(LineItem::getName,
lineItem -> Collectors.reducing(BigDecimal.ZERO,(a,b) -> ???)));
groupingBy
takes a Collector
as its second argument, so you should not pass the lambda lineItem -> ...
, and instead pass the Collector.reducing(...)
directly.
Also, since you are reducing a bunch of LineItem
s to one BigDecimal
, you should use the three-parameter overload of reducing
, with a mapper
public static <T, U> Collector<T,?,U> reducing(
U identity,
Function<? super T,? extends U> mapper,
BinaryOperator<U> op)
The mapper
is where you specify how a LineItem
into a BigDecimal
. You probably confused this with the second parameter of groupingBy
.
So to summarise:
Map<String,BigDecimal> totalByItem =
orders.stream()
.flatMap(order -> order.getLineItems().stream())
.collect(
Collectors.groupingBy(
LineItem::getName,
Collectors.reducing(
BigDecimal.ZERO,
LineItem::getPrice, // <----
BigDecimal::add
)
)
);
As Holger commented, the entire groupingBy
collector can also be replaced with a toMap
collector, without using reducing
at all.
.collect(
Collectors.toMap(
LineItem::getName, // key of the map
LineItem::getPrice, // value of the map
BigDecimal::add // what to do with the values when the keys duplicate
)
);