Java使用record關(guān)鍵詞統(tǒng)計(jì)余額的示例代碼
Java record 關(guān)鍵詞+ Map 匯總統(tǒng)計(jì)實(shí)戰(zhàn):一段余額統(tǒng)計(jì)代碼背后的設(shè)計(jì)思想
很多 Java 程序員在日常開發(fā)中都會(huì)遇到類似需求:
統(tǒng)計(jì)一批用戶當(dāng)前可用余額
但當(dāng)代碼寫出來后,往往會(huì)變成一坨:
- if/else 到處都是
- BigDecimal 累加混亂
- Map 使用不規(guī)范
- DTO 寫一堆模板代碼
最近在項(xiàng)目中看到一段非常典型的實(shí)現(xiàn)代碼,涉及:
- Java
record Map.getOrDefault- 批次余額統(tǒng)計(jì)
- BigDecimal 累加
- MyBatis LambdaQueryWrapper
雖然代碼不長(zhǎng),但里面隱藏著很多 值得學(xué)習(xí)的設(shè)計(jì)思想。
本文就帶大家 逐行拆解這段代碼,并分析背后的實(shí)現(xiàn)邏輯。
一、完整代碼
先看完整方法:
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;
}
這段代碼的核心作用其實(shí)就是:
統(tǒng)計(jì)一批用戶當(dāng)前可用余額(現(xiàn)金 + 贈(zèng)送金)
返回結(jié)構(gòu):
userId -> 余額匯總
二、余額匯總對(duì)象:Java record
代碼中使用了 record 定義余額對(duì)象:
public record BatchBalanceSummary(
BigDecimal cashBalance,
BigDecimal promoBalance) {
}
record 是 Java 16 引入的 數(shù)據(jù)類語法糖。
它會(huì)自動(dòng)生成:
- 構(gòu)造函數(shù)
- getter
- equals
- hashCode
- toString
傳統(tǒng)寫法:
public class BatchBalanceSummary {
private BigDecimal cashBalance;
private BigDecimal promoBalance;
}
至少要寫幾十行代碼。
而 record:1 行搞定
record 編譯后結(jié)構(gòu)
BatchBalanceSummary
│
├── cashBalance
├── promoBalance
├── equals()
├── hashCode()
└── toString()
三、整體執(zhí)行流程
這段代碼整體流程如下:

可以看到整個(gè)過程非常清晰:
- 輸入用戶列表
- 數(shù)據(jù)清洗
- 賬戶狀態(tài)校驗(yàn)
- 查詢批次余額
- 匯總統(tǒng)計(jì)
四、第一步:參數(shù)防御
if (userIds == null || userIds.isEmpty()) {
return Map.of();
}
這是典型的 防御式編程。
如果用戶列表為空:
直接返回:
{}
避免后面邏輯執(zhí)行。
Map.of() 是 Java 9 新特性:
返回 不可變 Map。
五、第二步:數(shù)據(jù)清洗
List<Long> distinctUserIds = userIds.stream()
.filter(Objects::nonNull)
.distinct()
.toList();
作用:
- 去掉
null - 去重
例如:
輸入:
[1001,1002,null,1001]
輸出:
[1001,1002]
這樣可以避免:
- 重復(fù)查詢數(shù)據(jù)庫(kù)
- 重復(fù)統(tǒng)計(jì)余額
六、第三步:確保錢包存在
ensureUserBatchWallet(userId);
這一步通常是:
初始化用戶錢包
如果用戶第一次使用余額系統(tǒng):
就創(chuàng)建錢包記錄。
例如:
user_wallet
表中插入一條數(shù)據(jù)。
七、第四步:處理余額過期
expireIfNeeded(userId);
余額系統(tǒng)通常都有:
- 贈(zèng)送金
- 優(yōu)惠金
- 過期時(shí)間
所以需要在統(tǒng)計(jì)前:
把過期余額標(biāo)記為失效
否則會(huì)統(tǒng)計(jì)到錯(cuò)誤金額。
八、第五步:初始化結(jié)果 Map
Map<Long, BatchBalanceSummary> result = new LinkedHashMap<>();
為什么用 LinkedHashMap?
因?yàn)椋嚎梢员3植迦腠樞?/p>
初始化結(jié)果:
1001 -> (0,0)
1002 -> (0,0)
代碼:
result.put(userId,
new BatchBalanceSummary(BigDecimal.ZERO, BigDecimal.ZERO));
九、第六步:查詢數(shù)據(jù)庫(kù)批次
List<AccountBatchEntity> batches =
accountBatchMapper.selectList(...)
SQL 等價(jià):
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(...)
作用:
獲取當(dāng)前用戶已經(jīng)累計(jì)的余額。
如果不存在:
返回默認(rèn):
(0,0)
判斷余額類型
ACCOUNT_TYPE_PROMO
如果是:
贈(zèng)送余額
就加到:
promoBalance
否則:
cashBalance
現(xiàn)金余額累加
current.cashBalance().add(amount)
贈(zèng)送余額累加
current.promoBalance().add(amount)
十一、余額統(tǒng)計(jì)示例
假設(shè)數(shù)據(jù)庫(kù)數(shù)據(jù):
| userId | type | amount |
|---|---|---|
| 1001 | cash | 100 |
| 1001 | promo | 20 |
| 1001 | cash | 30 |
| 1002 | promo | 50 |
統(tǒng)計(jì)過程:
1001 -> (0,0)
+100 cash
1001 -> (100,0)
+20 promo
1001 -> (100,20)
+30 cash
1001 -> (130,20)
最終:
1001 -> (130,20)
1002 -> (0,50)
十二、余額統(tǒng)計(jì)架構(gòu)圖
整個(gè)余額系統(tǒng)一般是這樣設(shè)計(jì):

十三、record 的優(yōu)勢(shì)
使用 record 后代碼變得非常干凈:
BatchBalanceSummary
只負(fù)責(zé):
數(shù)據(jù)承載
優(yōu)點(diǎn):
- 不可變對(duì)象
- 線程安全
- 減少模板代碼
- 更清晰的領(lǐng)域模型
十四、幾個(gè)可以優(yōu)化的地方
1 使用 computeIfAbsent
可以替換:
getOrDefault
寫法更優(yōu)雅:
result.computeIfAbsent(
userId,
k -> new BatchBalanceSummary(BigDecimal.ZERO, BigDecimal.ZERO)
);
2 SQL 聚合優(yōu)化
如果數(shù)據(jù)量很大:
可以用 SQL 聚合:
SELECT user_id,
account_type,
SUM(remaining_amount)
FROM account_batch
GROUP BY user_id,account_type減少 Java 層循環(huán)。
3 批量過期處理
目前代碼:
expireIfNeeded(userId)
如果用戶很多:
可能產(chǎn)生大量 SQL。
可以改成:
批量過期處理
十五、總結(jié)
這段代碼雖然只有幾十行,但實(shí)際上包含了很多優(yōu)秀的設(shè)計(jì)思想:
- record 數(shù)據(jù)對(duì)象
- Map 匯總統(tǒng)計(jì)
- 防御式編程
- BigDecimal 安全計(jì)算
- 批次余額設(shè)計(jì)
一句話總結(jié):
通過批次賬戶模型,實(shí)現(xiàn)用戶余額的安全、可擴(kuò)展統(tǒng)計(jì)。
這種設(shè)計(jì)在很多系統(tǒng)中都會(huì)出現(xiàn):
- 錢包系統(tǒng)
- 積分系統(tǒng)
- 余額系統(tǒng)
- 賬戶系統(tǒng)
如果你在做 支付 / 錢包 / 優(yōu)惠金系統(tǒng),這種設(shè)計(jì)模式基本是必備技能。
到此這篇關(guān)于Java使用record關(guān)鍵詞統(tǒng)計(jì)余額的示例代碼的文章就介紹到這了,更多相關(guān)Java統(tǒng)計(jì)余額內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解spring mvc(注解)上傳文件的簡(jiǎn)單例子
本篇文章主要介紹了spring mvc(注解)上傳文件的簡(jiǎn)單例子,具有一定的參考價(jià)值,有興趣的可以了解一下。2017-01-01
使用@TransactionalEventListener監(jiān)聽事務(wù)教程
這篇文章主要介紹了使用@TransactionalEventListener監(jiān)聽事務(wù)教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
Spring?Validation參數(shù)效驗(yàn)的各種使用姿勢(shì)總結(jié)
在實(shí)際項(xiàng)目中經(jīng)常需要對(duì)前段傳來的數(shù)據(jù)進(jìn)行校驗(yàn),下面這篇文章主要給大家介紹了關(guān)于Spring?Validation參數(shù)效驗(yàn)的各種使用姿勢(shì),文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04
關(guān)于SpringBoot整合Canal數(shù)據(jù)同步的問題
大家都知道canal是阿里巴巴旗下的一款開源工具,純java開發(fā),支持mysql數(shù)據(jù)庫(kù),本文給大家介紹SpringBoot整合Canal數(shù)據(jù)同步的問題,需要的朋友可以參考下2022-03-03
Java實(shí)現(xiàn)HTML轉(zhuǎn)PDF及合并PDF文件詳細(xì)教程
之前在工作中遇到過一個(gè)需求,是將用戶填寫的申報(bào),由固定模板的來生成pdf,后來想到先把內(nèi)容生成html,然后再把html格式的內(nèi)容轉(zhuǎn)換成pdf,這篇文章主要介紹了Java實(shí)現(xiàn)HTML轉(zhuǎn)PDF及合并PDF文件的相關(guān)資料,需要的朋友可以參考下2025-07-07

