Collectors.reducing 的正确语法

Correct syntax for Collectors.reducing

我有一个 Order class 和一个 LineItem class 如下所示:

@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.groupingByCollectors.reducing 结合使用,但努力使用正确的语法。有人可以帮忙吗?

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

你几乎完美了。您需要调用映射以从 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

groupingByCollector 作为其第二个参数,因此您不应传递 lambda lineItem -> ...,而应直接传递 Collector.reducing(...)

此外,由于您正在将一堆 LineItem 减少为一个 BigDecimal,您应该使用 reducingthree-parameter overload,以及 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
                )
            )
        );

正如 Holger 评论的那样,整个 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
    )
);