1. 引言

本文将介绍如何通过Java Stream API对元素进行分组,并找出每个分组中的最小值和最大值。需要读者具备基础的Java 8流式编程知识,更多细节可参考分组收集器

2. 理解使用场景

假设我们有一个订单项列表,每个订单项包含分类和价格字段。现在需要按分类分组,并找出每个分类中价格最低和最高的商品。

先定义订单项分类枚举:

public enum OrderItemCategory {
    BOOKS, CLOTHING, ELECTRONICS, FURNITURE, OTHER;
}

再定义订单项类:

public class OrderItem {
    private Long id;
    private Double price;
    private OrderItemCategory category;
    // 其他字段
    // getters和setters
}

3. 使用Streams API分组订单项

下面展示如何按分类分组并找出每组中的最低价和最高价商品:

Map<OrderItemCategory, Pair<Double, Double>> groupByCategoryWithMinMax(List<OrderItem> orderItems) {
    Map<OrderItemCategory, DoubleSummaryStatistics> categoryStatistics = orderItems.stream()
      .collect(Collectors.groupingBy(OrderItem::getCategory, Collectors.summarizingDouble(OrderItem::getPrice)));

    return categoryStatistics.entrySet().stream()
      .collect(Collectors.toMap(Map.Entry::getKey, entry -> Pair.of(entry.getValue().getMin(), entry.getValue().getMax())));
}

OrderProcessor类中的groupByCategoryWithMinMax()方法处理订单项列表:

  1. 首先通过Collectors.groupingBy按分类分组
  2. 使用Collectors.summarizingDouble收集价格统计信息
  3. 生成以分类为键、DoubleSummaryStatistics为值的映射(包含计数、总和、平均值、最小值和最大值)
  4. 最后转换为以分类为键、Pair<Double, Double>为值的映射(Pair中存储最小值和最大值)

3.1 理解DoubleSummaryStatistics

DoubleSummaryStatistics提供了一种高效收集double值流统计信息的方式。在流式处理中,它能一次性捕获:

  • 计数
  • 总和
  • 平均值
  • 最小值
  • 最大值

核心优势:单次遍历完成所有统计计算,特别适合大数据集处理。在示例中,我们用它直接获取每个分类的最低价和最高价,避免了手动计算的麻烦。

3.2 理解summarizingDouble收集器

summarizingDouble收集器是Collectors工具类中的方法,专门用于收集double值的统计信息。它返回一个DoubleSummaryStatistics对象,包含:

  • 计数
  • 总和
  • 平均值
  • 最小值
  • 最大值

⚠️ 性能提示:相比多次遍历数据,使用summarizingDouble只需单次遍历即可获取完整统计信息,是处理大数据集的利器。

4. 测试分组逻辑

通过单元测试验证分组逻辑:

@Test
void whenOrderItemsAreGrouped_thenGetsMinMaxPerGroup() {
    List<OrderItem> items =
      Arrays.asList(
        new OrderItem(1L, OrderItemCategory.ELECTRONICS, 1299.99),
        new OrderItem(2L, OrderItemCategory.ELECTRONICS, 1199.99),
        new OrderItem(3L, OrderItemCategory.ELECTRONICS, 2199.99),
        new OrderItem(4L, OrderItemCategory.FURNITURE, 220.00),
        new OrderItem(4L, OrderItemCategory.FURNITURE, 200.20),
        new OrderItem(5L, OrderItemCategory.FURNITURE, 215.00),
        new OrderItem(6L, OrderItemCategory.CLOTHING, 50.75),
        new OrderItem(7L, OrderItemCategory.CLOTHING, 75.00),
        new OrderItem(8L, OrderItemCategory.CLOTHING, 75.00));

    OrderProcessor orderProcessor = new OrderProcessor();
    Map<OrderItemCategory, Pair<Double, Double>> orderItemCategoryPairMap =
      orderProcessor.groupByCategoryWithMinMax(items);
    assertEquals(orderItemCategoryPairMap.get(OrderItemCategory.ELECTRONICS), Pair.of(1199.99, 2199.99));
    assertEquals(orderItemCategoryPairMap.get(OrderItemCategory.FURNITURE), Pair.of(200.20, 220.00));
    assertEquals(orderItemCategoryPairMap.get(OrderItemCategory.CLOTHING), Pair.of(50.75, 75.00));
}

测试逻辑:

  1. 创建包含不同分类和价格的订单项列表
  2. 调用groupByCategoryWithMinMax()方法分组并计算极值
  3. 验证每个分类的最低价和最高价是否符合预期

5. 总结

本文介绍了如何使用Java Stream API对数据进行分组并找出每组中的最小值和最大值。核心技巧包括:

  • 使用groupingBy进行分组
  • 通过summarizingDouble收集统计信息
  • 利用DoubleSummaryStatistics高效获取极值

完整示例代码可在GitHub获取。


原始标题:Guide to Finding Min and Max by Group Using Stream API | Baeldung