You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于accountId与invoiceDate字段合并列表中的多个AccountInfo对象

嘿,这个合并需求在业务开发里太常见了!我给你两种靠谱的实现方式,分别适合Java 8+的简洁流处理和传统循环的直观写法,你可以根据项目情况选:

方案一:Java 8+ Stream API 实现(简洁高效)

这种方式用Stream的分组和归约操作,代码简洁,适合已经熟悉Lambda表达式的场景。

首先我们需要一个分组键类,用来把accountId和invoiceDate组合成唯一标识(因为要同时匹配这两个字段才合并)。如果你用的是Java 16+,可以直接用record(自动生成equals和hashCode,省代码);如果是低版本,就自定义一个不可变类:

分组键定义(Java 16+)

// 记录类自动实现equals和hashCode,完美作为Map的键
record AccountKey(String accountId, Date invoiceDate) {}

分组键定义(Java 8-15)

import java.util.Date;
import java.util.Objects;

class AccountKey {
    private final String accountId;
    private final Date invoiceDate;

    public AccountKey(String accountId, Date invoiceDate) {
        this.accountId = accountId;
        this.invoiceDate = invoiceDate;
    }

    // 必须重写equals和hashCode,否则HashMap无法正确分组
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        AccountKey that = (AccountKey) o;
        return Objects.equals(accountId, that.accountId) && Objects.equals(invoiceDate, that.invoiceDate);
    }

    @Override
    public int hashCode() {
        return Objects.hash(accountId, invoiceDate);
    }
}

Stream合并代码

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

// 假设你的原列表是List<AccountInfo> accountInfoLst;
List<AccountInfo> mergedList = accountInfoLst.stream()
        // 按AccountKey分组
        .collect(Collectors.groupingBy(
                info -> new AccountKey(info.getAccountId(), info.getInvoiceDate()),
                // 归约每个分组的元素,合并vat和totalFee
                Collectors.reducing(
                        null, // 初始值
                        (acc, current) -> {
                            if (acc == null) {
                                // 第一个元素,创建新对象避免修改原列表的原始数据
                                AccountInfo newInfo = new AccountInfo();
                                newInfo.setAccountId(current.getAccountId());
                                newInfo.setInvoiceDate(current.getInvoiceDate());
                                newInfo.setVat(current.getVat());
                                newInfo.setTotalFee(current.getTotalFee());
                                return newInfo;
                            }
                            // 累加字段,注意处理null值(如果字段可能为空的话)
                            acc.setVat((acc.getVat() == null ? 0 : acc.getVat()) + (current.getVat() == null ? 0 : current.getVat()));
                            acc.setTotalFee((acc.getTotalFee() == null ? 0 : acc.getTotalFee()) + (current.getTotalFee() == null ? 0 : current.getTotalFee()));
                            return acc;
                        }
                )
        ))
        .values() // 取出所有合并后的AccountInfo
        .stream()
        .filter(Objects::nonNull) // 过滤空值(如果原列表为空的话)
        .collect(Collectors.toList());
方案二:传统循环+HashMap 实现(直观易调试)

如果团队里有人对Stream不太熟悉,或者需要更直观的调试流程,用传统循环+HashMap的方式更稳妥:

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

List<AccountInfo> mergedList = new ArrayList<>();
Map<AccountKey, AccountInfo> mergeMap = new HashMap<>();

for (AccountInfo current : accountInfoLst) {
    AccountKey key = new AccountKey(current.getAccountId(), current.getInvoiceDate());
    AccountInfo existing = mergeMap.get(key);

    if (existing == null) {
        // 分组里没有这个键,创建新对象存入Map
        AccountInfo newInfo = new AccountInfo();
        newInfo.setAccountId(current.getAccountId());
        newInfo.setInvoiceDate(current.getInvoiceDate());
        newInfo.setVat(current.getVat());
        newInfo.setTotalFee(current.getTotalFee());
        mergeMap.put(key, newInfo);
    } else {
        // 已经存在,累加字段
        existing.setVat((existing.getVat() == null ? 0 : existing.getVat()) + (current.getVat() == null ? 0 : current.getVat()));
        existing.setTotalFee((existing.getTotalFee() == null ? 0 : existing.getTotalFee()) + (current.getTotalFee() == null ? 0 : current.getTotalFee()));
    }
}

// 把Map里的合并结果转成List
mergedList.addAll(mergeMap.values());
关键注意点
  • Date的匹配精度:默认Date.equals()是精确到毫秒的,如果你的需求是“同一天就算匹配”(不管时分秒),需要先把invoiceDate截断到日期部分,比如:
    // 用Apache Commons Lang的工具类(推荐)
    Date truncatedDate = org.apache.commons.lang3.time.DateUtils.truncate(current.getInvoiceDate(), java.util.Calendar.DATE);
    // 或者自己手动截断
    java.util.Calendar cal = java.util.Calendar.getInstance();
    cal.setTime(current.getInvoiceDate());
    cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
    cal.set(java.util.Calendar.MINUTE, 0);
    cal.set(java.util.Calendar.SECOND, 0);
    cal.set(java.util.Calendar.MILLISECOND, 0);
    Date truncatedDate = cal.getTime();
    
    然后用truncatedDate作为AccountKey的一部分。
  • 空值处理:如果vat或totalFee可能为null,一定要在累加时做null判断,否则会抛出NullPointerException。
  • 避免修改原列表:上面的代码都是创建新的AccountInfo对象,而不是直接修改原列表里的元素,这样不会影响原始数据,更安全。

内容的提问来源于stack exchange,提问作者Aditya Thakur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 12:07:27