Правильный синтаксис для Collectors.reduction

У меня есть класс порядок и класс Позиция, как показано ниже:

@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) -> ???)));

Почему вы передали лямбду во второй параметр groupingBy? Это должно быть Collector.

Sweeper 21.03.2022 15:23

@Sweeper Я также пробовал без лямбды, Collectors.reducing(BigDecimal.ZERO,BigDecimal::add) но получаю ошибку Type parameter T has incompatible upper bounds: BigDecimal and LineItem Я думал, чтобы извлечь цену, мне нужно использовать лямбду!?

bbKing 21.03.2022 15:27

Вы сокращаете кучу LineItem до BigDecimal, поэтому вместо этого вы должны использовать 3-параметрическая перегрузка.

Sweeper 21.03.2022 15:31
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
3
49
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

У тебя получилось почти идеально. Вам нужно вызвать сопоставление, чтобы получить цену из 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 22.03.2022 09:52

@Holger Спасибо! Меня всегда поражало, как можно прийти к гораздо лучшему решению, просто поставив вопрос по-другому/подумав о нем с другой точки зрения, чего я, по общему признанию, часто не делаю.

Sweeper 22.03.2022 10:00

Другие вопросы по теме