按多属性合并对象集合的优雅实现

178 阅读2分钟

在日常开发中,我们经常需要处理对象集合的合并操作。下面记录下本人在实际开发中的一个场景,根据币种、摘要、辅助核算、科目四个属性对集合对象进行合并,并给出完整的实现方案。

一、需求场景分析

假设我们处理财务数据时,会遇到如下结构的数据集合:

List<AccountingEntry> entries = Arrays.asList(
    new AccountingEntry("USD", "销售收款", "客户A", "1001", 100.0),
    new AccountingEntry("USD", "销售收款", "客户A", "1001", 200.0),
    new AccountingEntry("CNY", "采购付款", "供应商B", "2001", 500.0)
);

需要将相同币种、摘要、辅助核算、科目的记录合并,金额相加,得到:

[ AccountingEntry("USD", "销售收款", "客户A", "1001", 300.0), AccountingEntry("CNY", "采购付款", "供应商B", "2001", 500.0) ]

二、实现方案

1. 定义数据模型

public class AccountingEntry {
    private String currency;      // 币种
    private String summary;       // 摘要
    private String auxiliaryCode; // 辅助核算
    private String subjectCode;   // 科目代码
    private double amount;        // 金额

    // 构造方法/getter/setter 略
}

2. 实现合并逻辑

使用Java Stream API进行高效分组合并:

public List<AccountingEntry> mergeEntries(List<AccountingEntry> entries) {
    return new ArrayList<>(entries.stream()
            .collect(Collectors.toMap(
                    entry -> Arrays.asList(
                            entry.getCurrency(),
                            entry.getSummary(),
                            entry.getAuxiliaryCode(),
                            entry.getSubjectCode()
                    ),
                    Function.identity(),
                    (existing, replacement) -> new AccountingEntry(
                            existing.getCurrency(),
                            existing.getSummary(),
                            existing.getAuxiliaryCode(),
                            existing.getSubjectCode(),
                            existing.getAmount() + replacement.getAmount()
                    )
            ))
            .values());
}

3. 使用Tuple优化(可选)

对于复杂key处理,建议创建专用类:

public class CompositeKey {
    private String currency;
    private String summary;
    private String auxiliaryCode;
    private String subjectCode;

    // 必须实现equals和hashCode
    @Override
    public boolean equals(Object o) { /*...*/ }
    
    @Override
    public int hashCode() { /*...*/ }
}

三、完整示例代码

public class AccountingMerger {

    public static void main(String[] args) {
        List<AccountingEntry> entries = // 初始化数据
        
        List<AccountingEntry> merged = mergeAccountingEntries(entries);
        merged.forEach(System.out::println);
    }

    public static List<AccountingEntry> mergeAccountingEntries(List<AccountingEntry> entries) {
        return entries.stream()
                .collect(Collectors.groupingBy(
                        entry -> new CompositeKey(
                                entry.getCurrency(),
                                entry.getSummary(),
                                entry.getAuxiliaryCode(),
                                entry.getSubjectCode()
                        ),
                        Collectors.summingDouble(AccountingEntry::getAmount)
                ))
                .entrySet().stream()
                .map(entry -> new AccountingEntry(
                        entry.getKey().getCurrency(),
                        entry.getKey().getSummary(),
                        entry.getKey().getAuxiliaryCode(),
                        entry.getKey().getSubjectCode(),
                        entry.getValue()
                ))
                .collect(Collectors.toList());
    }
}

// 复合Key类
class CompositeKey {
    // 成员变量、构造方法
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        CompositeKey that = (CompositeKey) o;
        return Objects.equals(currency, that.currency) &&
                Objects.equals(summary, that.summary) &&
                Objects.equals(auxiliaryCode, that.auxiliaryCode) &&
                Objects.equals(subjectCode, that.subjectCode);
    }

    @Override
    public int hashCode() {
        return Objects.hash(currency, summary, auxiliaryCode, subjectCode);
    }
}

四、关键点解析

  1. 分组策略:使用复合键(Composite Key)确保多字段的唯一性
  2. 金额合并:采用Collectors.summingDouble进行数值累加
  3. 性能优化:时间复杂度O(n),适合大数据量处理
  4. 可维护性:独立CompositeKey类便于后续扩展

五、扩展思考

  1. 处理精度问题:建议使用BigDecimal代替double
  2. 并发场景:使用parallelStream()并行处理
  3. 空值处理:增加字段校验逻辑

六、总结

本文通过Stream API实现了高效的对象合并方案,保证了代码的简洁性和可维护性。这种模式可广泛应用于财务系统、数据分析等需要聚合计算的场景。