java stream 分组统计
·
package com.ldj.springboot.importbean.utils;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
/**
* User: ldj
* Date: 2025/10/24
* Time: 0:14
* Description: 分组统计并检查是否超预算
*/
public class GroupStream {
public static void main(String[] args) {
// 模拟申请数据
List<ApplyInfo> applyList = new ArrayList<>();
applyList.add(new ApplyInfo("2025", "09", "09", "T1", new BigDecimal("100")));
applyList.add(new ApplyInfo("2024", "09", "09", "T2", new BigDecimal("100")));
applyList.add(new ApplyInfo("2025", "11", "11", "T1", new BigDecimal("200")));
// 配置年度预算上限
BigDecimal yearAmount = new BigDecimal("500");
// 1. 按 (year, deptCode, materialGroup) 分组,统计申请金额
Map<BudgetKey, BigDecimal> applyAmountMap = applyList.stream()
.collect(Collectors.groupingBy(
row -> new BudgetKey(row.getYear(), row.getDeptCode(), row.getMaterialGroup()),
Collectors.reducing(BigDecimal.ZERO, ApplyInfo::getAmount, BigDecimal::add)
));
// 2. 提取所有需要查询“已使用金额”的 key(拼接成字符串)
List<String> keysToQuery = applyAmountMap.keySet().stream().map(BudgetKey::getKey).collect(Collectors.toList());
// 3. 批量查询:只调一次接口/数据库,获取所有已使用金额
Map<String, BigDecimal> usedAmountMap = batchQueryUsedAmount(keysToQuery);
// 4. 对比每项是否超预算
List<String> exceededKeys = new ArrayList<>();
for (Map.Entry<BudgetKey, BigDecimal> entry : applyAmountMap.entrySet()) {
String key = BudgetKey.getKey(entry.getKey());
BigDecimal itemAmount = entry.getValue(); // 本次申请金额
BigDecimal usedAmount = usedAmountMap.getOrDefault(key, BigDecimal.ZERO); // 已使用金额(默认0)
BigDecimal total = itemAmount.add(usedAmount); // 总使用金额
if (total.compareTo(yearAmount) > 0) {
exceededKeys.add(key);
}
}
// 5. 如果有超预算的项,抛出异常
if (!CollectionUtils.isEmpty(exceededKeys)) {
throw new IllegalArgumentException(JSON.toJSONString(exceededKeys) + " 已经超出年度预算金额!");
}
System.out.println("提交成功!");
}
/**
* 模拟:批量查询“已使用金额”
* 在真实项目中,这里应调用 DAO 或 RPC 批量接口
*/
public static Map<String, BigDecimal> batchQueryUsedAmount(List<String> keys) {
Map<String, BigDecimal> mockData = new HashMap<>();
mockData.put("2025_09_T1", new BigDecimal("200"));
mockData.put("2024_09_T2", new BigDecimal("100"));
mockData.put("2025_11_T1", new BigDecimal("50"));
// 只返回请求的 keys 对应的数据(模拟数据库 IN 查询)
return keys.stream().collect(Collectors.toMap(key -> key, key -> mockData.getOrDefault(key, BigDecimal.ZERO)));
}
}
@Data
@AllArgsConstructor
class BudgetKey {
private String year;
private String deptCode;
private String materialGroup;
/**
* 将 BudgetKey 转为字符串 key,格式:year_deptCode_materialGroup
*/
public static String getKey(BudgetKey budgetKey) {
return String.join("_", budgetKey.getYear(), budgetKey.getDeptCode(), budgetKey.getMaterialGroup());
}
// 重写 equals 和 hashCode(重要!用于 groupingBy 正确分组)
// 懒重写 equals 和 hashCode 使用注解 @Data
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BudgetKey)) return false;
BudgetKey budgetKey = (BudgetKey) o;
return Objects.equals(year, budgetKey.year) &&
Objects.equals(deptCode, budgetKey.deptCode) &&
Objects.equals(materialGroup, budgetKey.materialGroup);
}
@Override
public int hashCode() {
return Objects.hash(year, deptCode, materialGroup);
}
}
@Data
@AllArgsConstructor
class ApplyInfo {
private String year;
private String month;
private String deptCode;
private String materialGroup;
private BigDecimal amount;
}
更多推荐

所有评论(0)