Stream API处理Map需先转为entrySet/keySet/values流;entrySet最常用,values最轻量;过滤应前置精准;转换要明确目标类型避免装箱;复杂规约优先用teeing或自定义Collector。

Stream API 处理 Map 不能直接“流化”整个 Map 对象,但可以高效转为 Entry 流,再按需过滤、转换、规约——关键在于把 Map 视为 键值对集合,而非黑盒容器。
从 Map 获取可操作的 Stream
Map 本身没有 .stream() 方法,必须先转为 entrySet、keySet 或 values 集合:
- entrySet().stream():最常用,保留键+值,适合条件依赖两者(如“value > 100 且 key 以 A 开头”)
- keySet().stream():只处理键,适合基于键筛选或映射(如提取所有用户 ID 字符串)
- values().stream():只处理值,适合纯业务对象聚合(如统计活跃订单总金额)
注意:不要用 Map.entrySet().stream() 后再 .map(Map.Entry::getValue) 来“模拟 values 流”,除非你后续还需用到 key ——否则直接 values().stream() 更轻量、无冗余对象创建。
过滤逻辑要前置且精准
Map 的 entry 流过滤,应优先利用键或值的天然属性减少数据量:
- 用 filter(e -> e.getValue() != null) 排除空值,避免下游 map 或 reduce 报 NPE
- 对字符串键,用 filter(e -> e.getKey().length() > 2) 比先 map 再 filter 更早剪枝
- 多条件组合推荐用 Predicate.and(),例如:
Predicate<Map.Entry<String, Integer>> valid = e -> e.getValue() > 0;
Predicate<Map.Entry<String, Integer>> nonTest = e -> !e.getKey().startsWith("test_");
stream.filter(valid.and(nonTest))
转换时明确目标类型,避免隐式装箱
Entry 流转换常见目标有三类,选错会引发性能陷阱:
- 转键或值的简单集合:直接 map(Map.Entry::getKey) 或 map(Map.Entry::getValue),配合 toList()/toSet()
-
转新 Map:用 collect(Collectors.toMap()),注意键冲突时指定 merge 函数,例如:
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1)) - 转数值型流做聚合:若 value 是数字,优先用 mapToInt(Map.Entry::getValue),避免 Integer 装箱;求和、平均、最大值都更高效
复杂规约用 teeing 或自定义收集器单次完成
一个典型需求:从 Map
-
Java 12+ 推荐 teeing:
Collectors.teeing(
Collectors.summingDouble(e -> e.getValue().getAmount()),
Collectors.maxBy(Comparator.comparing(e -> e.getValue().getUnitPrice())),
(sum, maxOpt) -> new Summary(sum, maxOpt.map(e -> e.getKey()).orElse(""))
) - 老版本可用自定义 Collector:实现 Supplier、BiConsumer、BinaryOperator,手动累积多个状态变量,比多次流调用稳定可控
- 避免在 reduce 中拼接 List 或 StringBuilder —— 它不是线程安全的,也不符合归约的不可变语义;该用 collect 就别硬套 reduce


















