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)
十一、余额统计示例
假设数据库数据:
| userid | type | amount |
|---|---|---|
| 1001 | cash | 100 |
| 1001 | promo | 20 |
| 1001 | cash | 30 |
| 1002 | promo | 50 |
统计过程:
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统计余额内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论