У меня есть класс порядок и класс Позиция, как показано ниже:
@AllArgsConstructor
@Getter
@ToString
static class Order {
long orderId;
List<LineItem> lineItems;
}
@AllArgsConstructor
@Getter
@ToString
static class LineItem {
String name;
BigDecimal price;
}
и список заказов, из которых я хочу получить карту Map<String,BigDecimal> totalByItem
, где ключ - это имя LineItem и значение общей цены из всех заказов в списке. Для этого я хочу использовать Collectors.groupingBy в сочетании с Коллекторы.редукторы, но борюсь с правильным синтаксисом. Кто-нибудь может помочь?
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)))));
что поставить там где сейчас ????
Map<String,BigDecimal> totalByItem =
orders.stream()
.flatMap(order -> order.getLineItems().stream())
.collect(Collectors.groupingBy(LineItem::getName,
lineItem -> Collectors.reducing(BigDecimal.ZERO,(a,b) -> ???)));
@Sweeper Я также пробовал без лямбды, Collectors.reducing(BigDecimal.ZERO,BigDecimal::add)
но получаю ошибку Type parameter T has incompatible upper bounds: BigDecimal and LineItem
Я думал, чтобы извлечь цену, мне нужно использовать лямбду!?
Вы сокращаете кучу LineItem
до BigDecimal
, поэтому вместо этого вы должны использовать 3-параметрическая перегрузка.
У тебя получилось почти идеально. Вам нужно вызвать сопоставление, чтобы получить цену из LineItem. Затем вы можете выполнить операцию уменьшения, как показано ниже.
Map<String, BigDecimal> totalByItem = orders.stream()
.flatMap(order -> order.getLineItems().stream())
.collect(Collectors.groupingBy(LineItem::getName,
Collectors.mapping(LineItem::getPrice,
Collectors.reducing(
BigDecimal.ZERO, BigDecimal::add))));
Обратите внимание, что вы могли бы выполнить операцию сокращения следующим образом:
Collectors.reducing(BigDecimal.ZERO, (a,b)->a.add(b)))));
С вашими текущими данными вот что печатает
totalByItem.entrySet().forEach(System.out::println);
Item-A=2
Item-E=5
Item-D=8
Item-C=6
Item-B=4
groupingBy
принимает Collector
в качестве второго аргумента, поэтому вам не следует передавать лямбду lineItem -> ...
, а вместо этого передавать Collector.reducing(...)
напрямую.
Кроме того, поскольку вы сокращаете количество LineItem
до одного BigDecimal
, вы должны использовать трехпараметрическая перегрузкаreducing
с mapper
public static <T, U> Collector<T,?,U> reducing(
U identity,
Function<? super T,? extends U> mapper,
BinaryOperator<U> op)
mapper
— это место, где вы указываете, как LineItem
превратиться в BigDecimal
. Вы, вероятно, перепутали это со вторым параметром groupingBy
.
Итак, резюмируя:
Map<String,BigDecimal> totalByItem =
orders.stream()
.flatMap(order -> order.getLineItems().stream())
.collect(
Collectors.groupingBy(
LineItem::getName,
Collectors.reducing(
BigDecimal.ZERO,
LineItem::getPrice, // <----
BigDecimal::add
)
)
);
Как заметил Хольгер, весь коллектор groupingBy
также можно заменить коллектором toMap
, вообще не используя reducing
.
.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
)
);
Как (почти) всегда при сочетании groupingBy
с reducing
простое использование toMap
будет проще и потенциально эффективнее: Collectors.toMap(LineItem::getName, LineItem::getPrice, BigDecimal::add)
@Holger Спасибо! Меня всегда поражало, как можно прийти к гораздо лучшему решению, просто поставив вопрос по-другому/подумав о нем с другой точки зрения, чего я, по общему признанию, часто не делаю.
Почему вы передали лямбду во второй параметр
groupingBy
? Это должно бытьCollector
.