Java多条件分组聚合Payment对象:求和属性并保留固定属性
Java多条件分组合并Payment集合:求和聚合+保留一致属性
需求明确
对Payment对象集合按指定维度(date、paymentGroup)分组,对amount和quantity字段求和聚合,同时保留同组内值一致的属性(currency、type)。输入集合无排序要求,需Java 11+简洁可扩展的实现方案。
先补全Payment类(可运行基础代码)
原示例类需补充构造方法、Getter、toString方法,确保代码可执行:
import java.math.BigDecimal; import java.time.LocalDate; import java.util.Objects; public class Payment { private BigDecimal amount; // 求和聚合 private Long quantity; // 求和聚合 private LocalDate date; // 分组条件1 private String paymentGroup; // 分组条件2 private String currency; // 原样保留(同组值一致) private String type; // 原样保留(同组值一致) // 全参构造 public Payment(BigDecimal amount, Long quantity, LocalDate date, String paymentGroup, String currency, String type) { this.amount = amount; this.quantity = quantity; this.date = date; this.paymentGroup = paymentGroup; this.currency = currency; this.type = type; } // Getter方法 public BigDecimal getAmount() { return amount; } public Long getQuantity() { return quantity; } public LocalDate getDate() { return date; } public String getPaymentGroup() { return paymentGroup; } public String getCurrency() { return currency; } public String getType() { return type; } // 重写toString,方便输出验证 @Override public String toString() { return "Payment[" + amount + ", " + quantity + ", " + date + ", " + paymentGroup + ", " + currency + ", " + type + "]"; } }
方案一:Java原生Stream + 分组键(可扩展)
1. 定义分组键类
为了清晰、可扩展地定义分组条件,我们创建一个不可变的分组键类,包含所有分组维度字段:
import java.time.LocalDate; import java.util.Objects; // Java 11版本的不可变分组键(Java 16+可直接用record替代) public class GroupKey { private final LocalDate date; private final String paymentGroup; public GroupKey(LocalDate date, String paymentGroup) { this.date = date; this.paymentGroup = paymentGroup; } // Getter public LocalDate getDate() { return date; } public String getPaymentGroup() { return paymentGroup; } // 必须重写equals和hashCode,确保分组逻辑正确 @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(date, groupKey.date) && Objects.equals(paymentGroup, groupKey.paymentGroup); } @Override public int hashCode() { return Objects.hash(date, paymentGroup); } } // Java 16+可简化为: // public record GroupKey(LocalDate date, String paymentGroup) {}
2. 分组合并核心代码
利用Collectors.groupingBy分组,搭配Collectors.reducing完成组内合并:
import java.math.BigDecimal; import java.time.LocalDate; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; public class PaymentMerger { public static void main(String[] args) { // 构造输入数据 List<Payment> payments = List.of( new Payment(new BigDecimal(100), 15L, LocalDate.of(2023,7,20), "G1", "EUR", "Lorem ipsum"), new Payment(new BigDecimal(200), 10L, LocalDate.of(2023,7,20), "G1", "EUR", "Lorem ipsum"), new Payment(new BigDecimal(300), 10L, LocalDate.of(2023,7,20), "G2", "EUR", "Lorem dipsum"), new Payment(new BigDecimal(400), 20L, LocalDate.of(2023,7,20), "G2", "EUR", "Lorem dipsum"), new Payment(new BigDecimal(500), 25L, LocalDate.of(2023,7,21), "G2", "EUR", "Ipsum dolor") ); // 分组合并逻辑 List<Payment> mergedPayments = payments.stream() .collect(Collectors.groupingBy( // 按分组键分组 payment -> new GroupKey(payment.getDate(), payment.getPaymentGroup()), // 组内合并:求和聚合字段,保留其他属性(取组内第一个元素的属性) Collectors.reducing((p1, p2) -> new Payment( p1.getAmount().add(p2.getAmount()), p1.getQuantity() + p2.getQuantity(), p1.getDate(), p1.getPaymentGroup(), p1.getCurrency(), p1.getType() )) )) .values() // 获取所有合并后的Optional<Payment> .stream() .filter(Optional::isPresent) // 过滤空值(单元素组不会为空) .map(Optional::get) .collect(Collectors.toList()); // 输出验证 mergedPayments.forEach(System.out::println); } }
方案优势
- 可扩展性极强:如果需要新增分组条件,只需修改
GroupKey的字段(比如加一个status字段),无需改动分组合并的核心逻辑。 - 原生Java实现:无第三方库依赖,兼容Java 11+。
- 逻辑清晰:分组键与合并逻辑分离,易于维护。
方案二:简化分组键(直接用List作为键)
如果不想额外创建GroupKey类,可直接用List.of(date, paymentGroup)作为分组键(仅适用于分组字段数量少的场景):
List<Payment> mergedPayments = payments.stream() .collect(Collectors.groupingBy( payment -> List.of(payment.getDate(), payment.getPaymentGroup()), Collectors.reducing((p1, p2) -> new Payment( p1.getAmount().add(p2.getAmount()), p1.getQuantity() + p2.getQuantity(), p1.getDate(), p1.getPaymentGroup(), p1.getCurrency(), p1.getType() )) )) .values() .stream() .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList());
注意:此方式的可读性不如自定义GroupKey,新增分组条件时需要修改List.of的参数,适合快速实现简单场景。
异常处理补充
如果业务上存在同组内currency或type不一致的情况,可在合并时添加校验逻辑,比如:
Collectors.reducing((p1, p2) -> { // 校验同组属性一致性 if (!Objects.equals(p1.getCurrency(), p2.getCurrency())) { throw new IllegalArgumentException("同组内currency不一致:" + p1.getCurrency() + " vs " + p2.getCurrency()); } if (!Objects.equals(p1.getType(), p2.getType())) { throw new IllegalArgumentException("同组内type不一致:" + p1.getType() + " vs " + p2.getType()); } // 求和聚合 return new Payment( p1.getAmount().add(p2.getAmount()), p1.getQuantity() + p2.getQuantity(), p1.getDate(), p1.getPaymentGroup(), p1.getCurrency(), p1.getType() ); })
内容的提问来源于stack exchange,提问作者AeonPhx
相关产品推荐
相关产品推荐

