使用Java Stream按多属性分组聚合min和max值的实现问题
解决方案:单流处理完成分组+归约+转换
直接通过一次Stream操作,结合分组与归约逻辑,完成多属性分组、字段求和及目标对象转换,避免繁琐的多次流处理。
前提假设
先明确相关类的结构(可根据实际调整):
// 原始数据类 class OccupancyEntry { private String attribute1; private String attribute2; private String attribute3; private int min; private int max; // 构造器、getter方法 public String getAttribute1() { return attribute1; } public String getAttribute2() { return attribute2; } public String getAttribute3() { return attribute3; } public int getMin() { return min; } public int getMax() { return max; } } // 聚合结果类 class OccupancyAggregated { private String attribute1; private String attribute2; private String attribute3; private int totalMin; private int totalMax; public OccupancyAggregated(String attribute1, String attribute2, String attribute3, int totalMin, int totalMax) { this.attribute1 = attribute1; this.attribute2 = attribute2; this.attribute3 = attribute3; this.totalMin = totalMin; this.totalMax = totalMax; } // getter方法(按需添加) public int getTotalMin() { return totalMin; } public int getTotalMax() { return totalMax; } }
方法1:使用Collectors.toMap直接合并(最简洁)
利用toMap的分组键、初始映射、合并函数,一步完成分组与累加:
List<OccupancyAggregated> aggregatedList = occupancyEntries.stream() .collect(Collectors.toMap( // 分组键:用三个属性组成自定义键(Java16+推荐用record,自动实现equals/hashCode) entry -> new GroupKey(entry.getAttribute1(), entry.getAttribute2(), entry.getAttribute3()), // 初始映射:将原始对象转为聚合对象,初始化求和字段 entry -> new OccupancyAggregated( entry.getAttribute1(), entry.getAttribute2(), entry.getAttribute3(), entry.getMin(), entry.getMax() ), // 合并函数:同一分组下累加min和max (agg1, agg2) -> new OccupancyAggregated( agg1.getAttribute1(), agg1.getAttribute2(), agg1.getAttribute3(), agg1.getTotalMin() + agg2.getTotalMin(), agg1.getTotalMax() + agg2.getTotalMax() ) )) // 将Map中的聚合值转为列表 .values().stream() .collect(Collectors.toList());
分组键实现(Java16+用record)
// Java16+ 自动生成正确的equals和hashCode,无需手动实现 record GroupKey(String attribute1, String attribute2, String attribute3) {}
分组键实现(Java8)
如果用Java8,需手动实现equals和hashCode:
class GroupKey { private final String attribute1; private final String attribute2; private final String attribute3; public GroupKey(String attribute1, String attribute2, String attribute3) { this.attribute1 = attribute1; this.attribute2 = attribute2; this.attribute3 = attribute3; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; GroupKey groupKey = (GroupKey) o; return Objects.equals(attribute1, groupKey.attribute1) && Objects.equals(attribute2, groupKey.attribute2) && Objects.equals(attribute3, groupKey.attribute3); } @Override public int hashCode() { return Objects.hash(attribute1, attribute2, attribute3); } // getter方法 public String getAttribute1() { return attribute1; } public String getAttribute2() { return attribute2; } public String getAttribute3() { return attribute3; } }
方法2:使用groupingBy+reducing
先按分组键分组,再对每组元素归约累加,最后转换为聚合对象:
List<OccupancyAggregated> aggregatedList = occupancyEntries.stream() .collect(Collectors.groupingBy( entry -> new GroupKey(entry.getAttribute1(), entry.getAttribute2(), entry.getAttribute3()), // 归约逻辑:合并两个原始对象,累加min和max Collectors.reducing( new OccupancyEntry("", "", "", 0, 0), // 初始空对象 (entry1, entry2) -> new OccupancyEntry( entry1.getAttribute1(), // 同一分组属性一致,用任意一个即可 entry1.getAttribute2(), entry1.getAttribute3(), entry1.getMin() + entry2.getMin(), entry1.getMax() + entry2.getMax() ) ) )) // 将分组结果转为聚合对象列表 .entrySet().stream() .map(entry -> { GroupKey key = entry.getKey(); OccupancyEntry reducedEntry = entry.getValue(); return new OccupancyAggregated( key.getAttribute1(), key.getAttribute2(), key.getAttribute3(), reducedEntry.getMin(), reducedEntry.getMax() ); }) .collect(Collectors.toList());
关键优化点
- 避免多次流处理:上述两种方法均通过一次Stream操作完成所有逻辑,无需拆分步骤,提升代码简洁性与执行效率。
- 分组键正确性:必须保证分组键的
equals和hashCode方法正确实现,否则分组会失效(Java16+的record可自动解决此问题)。 - 归约逻辑简化:如果允许给
OccupancyAggregated添加合并方法,可进一步简化代码:
// 在OccupancyAggregated中添加合并方法 public OccupancyAggregated merge(OccupancyAggregated other) { this.totalMin += other.totalMin; this.totalMax += other.totalMax; return this; } // 此时toMap的合并函数可简化为方法引用 (agg1, agg2) -> agg1.merge(agg2)
内容的提问来源于stack exchange,提问作者Bismal
相关产品推荐
相关产品推荐

