在业务开发中,我们经常会拿到这样一类数据:
{
"supplierId": 56,
"supplierName": "雄安铁建集团",
"dailyInAmount": {
"2025-09-14": 170.00,
"2025-09-13": 180.00,
"2025-09-12": 190.00,
"2025-09-11": 200.00,
"2025-09-18": 130.00,
...
}
}
这是一个 Map<String, BigDecimal>(key 是日期字符串,value 是金额)。由于 HashMap 的遍历顺序是无序的,前端拿到的 dailyInAmount 会是乱序的日期。
实际效果可能是这样的:
"2025-09-14": 170.00,
"2025-09-13": 180.00,
"2025-09-30": 10.00,
...
而我们的需求是:按日期升序输出,这样前端展示就是有序的时间轴:
"2025-09-01": 300.00,
"2025-09-02": 290.00,
"2025-09-03": 280.00,
...
Stream 解决方案
Java 8 之后有了 Stream API,可以很优雅地完成这种“对 Map 按键排序并收集”的需求:
// dailyAmount: Map<String, BigDecimal> 原始数据
Map<String, BigDecimal> sortedDailyAmount = dailyAmount.entrySet().stream()
.sorted(Map.Entry.comparingByKey()) // 按 key 排序
.collect(Collectors.toMap(
Map.Entry::getKey, // key 提取器
Map.Entry::getValue, // value 提取器
(e1, e2) -> e1, // 冲突时取第一个(一般不会冲突)
LinkedHashMap::new // 最终 Map 类型:按插入顺序
));
planVO.setDailyInAmount(sortedDailyAmount);
核心点:
entrySet().stream():Map本身不是Collection,没有stream();要流式处理必须先取entrySet()。.sorted(Map.Entry.comparingByKey()):对 entry 流按 key 自然顺序排序。这里 key 是yyyy-MM-dd格式的字符串,字典序 = 时间顺序。Collectors.toMap(...)的 4 个参数:- key 映射器
- value 映射器
- 冲突合并策略
- 最终 Map 类型
- 选择
LinkedHashMap是为了保留插入顺序(我们已经按日期排序后再插入,所以迭代就是日期顺序)。
效果
经过这段代码处理后,前端返回的数据就是按日期顺序排列的:
"dailyInAmount": {
"2025-09-01": 300.00,
"2025-09-02": 290.00,
"2025-09-03": 280.00,
"2025-09-04": 270.00,
...
}
常见变体
- 按 value 排序:
.sorted(Map.Entry.<String,BigDecimal>comparingByValue().reversed()) - 按真实时间排序(字符串不规则时):
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd"); .sorted(Comparator.comparing(e -> LocalDate.parse(e.getKey(), dtf))) - 用 TreeMap 简化:
Map<String,BigDecimal> sorted = new TreeMap<>(dailyAmount);
TreeMap 会自动按 key 排序,不过返回的是 SortedMap,不是按插入顺序的 LinkedHashMap;视需求选择。
总结
Map本身没有stream(),要处理键值对请用entrySet().stream()。sorted + Collectors.toMap是 Java 8 流处理 Map 排序的经典用法。- 指定
LinkedHashMap::new可以让结果按我们排序后的顺序迭代。 - 对日期、金额这类业务数据,前端展示通常要求有序,这就是这种用法的典型场景。
这段代码简洁优雅地完成了“无序 Map → 有序 Map”的转换,是 Stream 在日常业务里非常常见的一种用法。
