Java使用record关键词统计余额的示例代码

 更新时间:2026年04月23日 08:43:30   作者:码农阿豪@新空间  
很多 Java 程序员在日常开发中都会遇到类似需求,那就是统计一批用户当前可用余额,本文解析了一段Java余额统计代码的设计思想,感兴趣的小伙伴可以了解下

Java record 关键词+ Map 汇总统计实战:一段余额统计代码背后的设计思想

很多 Java 程序员在日常开发中都会遇到类似需求:

统计一批用户当前可用余额

但当代码写出来后,往往会变成一坨:

  • if/else 到处都是
  • BigDecimal 累加混乱
  • Map 使用不规范
  • DTO 写一堆模板代码

最近在项目中看到一段非常典型的实现代码,涉及:

  • Java record
  • Map.getOrDefault
  • 批次余额统计
  • BigDecimal 累加
  • MyBatis LambdaQueryWrapper

虽然代码不长,但里面隐藏着很多 值得学习的设计思想

本文就带大家 逐行拆解这段代码,并分析背后的实现逻辑

一、完整代码

先看完整方法:

public Map<Long, BatchBalanceSummary> summarizeAvailableBalances(List<Long> userIds) {
    if (userIds == null || userIds.isEmpty()) {
        return Map.of();
    }

    List<Long> distinctUserIds = userIds.stream()
        .filter(java.util.Objects::nonNull)
        .distinct()
        .toList();

    if (distinctUserIds.isEmpty()) {
        return Map.of();
    }

    for (Long userId : distinctUserIds) {
        ensureUserBatchWallet(userId);
        expireIfNeeded(userId);
    }

    Map<Long, BatchBalanceSummary> result = new LinkedHashMap<>();

    for (Long userId : distinctUserIds) {
        result.put(userId, new BatchBalanceSummary(BigDecimal.ZERO, BigDecimal.ZERO));
    }

    List<AccountBatchEntity> batches = accountBatchMapper.selectList(
        new LambdaQueryWrapper<AccountBatchEntity>()
            .in(AccountBatchEntity::getUserId, distinctUserIds)
            .eq(AccountBatchEntity::getStatus, STATUS_ACTIVE)
            .gt(AccountBatchEntity::getRemainingAmount, BigDecimal.ZERO)
    );

    for (AccountBatchEntity batch : batches) {

        BatchBalanceSummary current =
            result.getOrDefault(batch.getUserId(),
                new BatchBalanceSummary(BigDecimal.ZERO, BigDecimal.ZERO));

        if (ACCOUNT_TYPE_PROMO.equalsIgnoreCase(batch.getAccountType())) {

            result.put(batch.getUserId(),
                new BatchBalanceSummary(
                    current.cashBalance(),
                    current.promoBalance()
                        .add(normalize(batch.getRemainingAmount()))
                ));

        } else {

            result.put(batch.getUserId(),
                new BatchBalanceSummary(
                    current.cashBalance()
                        .add(normalize(batch.getRemainingAmount())),
                    current.promoBalance()
                ));
        }
    }

    return result;
}

这段代码的核心作用其实就是:

统计一批用户当前可用余额(现金 + 赠送金)

返回结构:

userId -> 余额汇总

二、余额汇总对象:Java record

代码中使用了 record 定义余额对象:

public record BatchBalanceSummary(
        BigDecimal cashBalance,
        BigDecimal promoBalance) {
}

record 是 Java 16 引入的 数据类语法糖

它会自动生成:

  • 构造函数
  • getter
  • equals
  • hashCode
  • toString

传统写法:

public class BatchBalanceSummary {

    private BigDecimal cashBalance;
    private BigDecimal promoBalance;

}

至少要写几十行代码。

而 record:1 行搞定

record 编译后结构

BatchBalanceSummary

├── cashBalance
├── promoBalance
├── equals()
├── hashCode()
└── toString()

三、整体执行流程

这段代码整体流程如下:

可以看到整个过程非常清晰:

  • 输入用户列表
  • 数据清洗
  • 账户状态校验
  • 查询批次余额
  • 汇总统计

四、第一步:参数防御

if (userIds == null || userIds.isEmpty()) {
    return Map.of();
}

这是典型的 防御式编程

如果用户列表为空:

直接返回:

{}

避免后面逻辑执行。

Map.of() 是 Java 9 新特性:

返回 不可变 Map

五、第二步:数据清洗

List<Long> distinctUserIds = userIds.stream()
        .filter(Objects::nonNull)
        .distinct()
        .toList();

作用:

  • 去掉 null
  • 去重

例如:

输入:

[1001,1002,null,1001]

输出:

[1001,1002]

这样可以避免:

  • 重复查询数据库
  • 重复统计余额

六、第三步:确保钱包存在

ensureUserBatchWallet(userId);

这一步通常是:

初始化用户钱包

如果用户第一次使用余额系统:

就创建钱包记录。

例如:

user_wallet

表中插入一条数据。

七、第四步:处理余额过期

expireIfNeeded(userId);

余额系统通常都有:

  • 赠送金
  • 优惠金
  • 过期时间

所以需要在统计前:

把过期余额标记为失效

否则会统计到错误金额。

八、第五步:初始化结果 Map

Map<Long, BatchBalanceSummary> result = new LinkedHashMap<>();

为什么用 LinkedHashMap

因为:可以保持插入顺序

初始化结果:

1001 -> (0,0)
1002 -> (0,0)

代码:

result.put(userId,
    new BatchBalanceSummary(BigDecimal.ZERO, BigDecimal.ZERO));

九、第六步:查询数据库批次

List<AccountBatchEntity> batches =
    accountBatchMapper.selectList(...)

SQL 等价:

SELECT *
FROM account_batch
WHERE user_id IN (...)
AND status = ACTIVE
AND remaining_amount > 0

查询条件:

条件含义
user_id指定用户
status批次有效
remaining_amount > 0还有余额

十、第七步:余额累加

核心代码:

BatchBalanceSummary current =
    result.getOrDefault(...)

作用:

获取当前用户已经累计的余额。

如果不存在:

返回默认:

(0,0)

判断余额类型

ACCOUNT_TYPE_PROMO

如果是:

赠送余额

就加到:

promoBalance

否则:

cashBalance

现金余额累加

current.cashBalance().add(amount)

赠送余额累加

current.promoBalance().add(amount)

十一、余额统计示例

假设数据库数据:

userIdtypeamount
1001cash100
1001promo20
1001cash30
1002promo50

统计过程:

1001 -> (0,0)

+100 cash
1001 -> (100,0)

+20 promo
1001 -> (100,20)

+30 cash
1001 -> (130,20)

最终:

1001 -> (130,20)
1002 -> (0,50)

十二、余额统计架构图

整个余额系统一般是这样设计:

十三、record 的优势

使用 record 后代码变得非常干净:

BatchBalanceSummary

只负责:

数据承载

优点:

  • 不可变对象
  • 线程安全
  • 减少模板代码
  • 更清晰的领域模型

十四、几个可以优化的地方

1 使用 computeIfAbsent

可以替换:

getOrDefault

写法更优雅:

result.computeIfAbsent(
    userId,
    k -> new BatchBalanceSummary(BigDecimal.ZERO, BigDecimal.ZERO)
);

2 SQL 聚合优化

如果数据量很大:

可以用 SQL 聚合:

SELECT user_id,
       account_type,
       SUM(remaining_amount)
FROM account_batch
GROUP BY user_id,account_type

减少 Java 层循环。

3 批量过期处理

目前代码:

expireIfNeeded(userId)

如果用户很多:

可能产生大量 SQL。

可以改成:

批量过期处理

十五、总结

这段代码虽然只有几十行,但实际上包含了很多优秀的设计思想:

  • record 数据对象
  • Map 汇总统计
  • 防御式编程
  • BigDecimal 安全计算
  • 批次余额设计

一句话总结:

通过批次账户模型,实现用户余额的安全、可扩展统计。

这种设计在很多系统中都会出现:

  • 钱包系统
  • 积分系统
  • 余额系统
  • 账户系统

如果你在做 支付 / 钱包 / 优惠金系统,这种设计模式基本是必备技能。

到此这篇关于Java使用record关键词统计余额的示例代码的文章就介绍到这了,更多相关Java统计余额内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot使用外置的Servlet容器的方法步骤

    SpringBoot使用外置的Servlet容器的方法步骤

    SpringBoot 是一个非常流行的 Java 开发框架,它提供了一个简单而强大的方式来创建基于 Servlet 容器的 Web 应用程序,本文将介绍 SpringBoot 中如何使用 Servlet 容器,需要的朋友可以参考下
    2024-12-12
  • 详解spring mvc(注解)上传文件的简单例子

    详解spring mvc(注解)上传文件的简单例子

    本篇文章主要介绍了spring mvc(注解)上传文件的简单例子,具有一定的参考价值,有兴趣的可以了解一下。
    2017-01-01
  • 使用@TransactionalEventListener监听事务教程

    使用@TransactionalEventListener监听事务教程

    这篇文章主要介绍了使用@TransactionalEventListener监听事务教程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • Spring Validation参数效验的各种使用姿势总结

    Spring Validation参数效验的各种使用姿势总结

    在实际项目中经常需要对前段传来的数据进行校验,下面这篇文章主要给大家介绍了关于Spring Validation参数效验的各种使用姿势,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-04-04
  • java中XML的使用全过程

    java中XML的使用全过程

    这篇文章主要介绍了java中XML的使用全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-05-05
  • 详解java如何集成swagger组件

    详解java如何集成swagger组件

    今天给大家带来的是关于Java的相关知识,文章围绕着java如何集成swagger组件展开,文中有非常详细的介绍及代码示例,需要的朋友可以参考下
    2021-06-06
  • Java中Switch用法代码示例

    Java中Switch用法代码示例

    这篇文章主要介绍了Java中Switch用法代码示例,向大家分享了两段代码,具有一定参考价值,需要的朋友可以了解下。
    2017-11-11
  • 关于SpringBoot整合Canal数据同步的问题

    关于SpringBoot整合Canal数据同步的问题

    大家都知道canal是阿里巴巴旗下的一款开源工具,纯java开发,支持mysql数据库,本文给大家介绍SpringBoot整合Canal数据同步的问题,需要的朋友可以参考下
    2022-03-03
  • Java实现HTML转PDF及合并PDF文件详细教程

    Java实现HTML转PDF及合并PDF文件详细教程

    之前在工作中遇到过一个需求,是将用户填写的申报,由固定模板的来生成pdf,后来想到先把内容生成html,然后再把html格式的内容转换成pdf,这篇文章主要介绍了Java实现HTML转PDF及合并PDF文件的相关资料,需要的朋友可以参考下
    2025-07-07
  • 微服务框架FEIGN使用常见问题分析

    微服务框架FEIGN使用常见问题分析

    这篇文章主要为大家介绍了微服务框架FEIGN常见问题分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08

最新评论