Collectors.toMap默认不接受null键或值,需在filter阶段显式排除;遇重复key抛IllegalStateException,应选合适重载并指定mergeFunction和mapFactory以保序或排序。

用 filter + Collectors.toMap 过滤 Map 时,Key 或 Value 为 null 会直接抛 NullPointerException
Java 的 Collectors.toMap 默认不接受 null 键或值,哪怕源 Map 里本来就有 null,只要进到收集阶段就会炸。这不是 Stream 懒加载能绕开的——收集动作本身强制校验。
常见错误现象:java.lang.NullPointerException: key must not be null 或同理的 value 报错。
- 如果原始
Map中存在null键/值,必须在filter阶段显式排除,不能依赖后续处理 - 也可以换用允许
null的收集器,比如Collectors.toMap(k -> k, v -> v, (a,b) -> a, HashMap::new),但注意第三个参数(merge function)只在键冲突时触发,不解决null本身问题 - 更稳妥的做法是:先
filter掉null键和值,再用默认toMap
示例:
Map<String, Integer> filtered = original.entrySet().stream()
.filter(e -> e.getKey() != null && e.getValue() != null)
.filter(e -> e.getValue() > 10)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
Collectors.toMap 的三个重载版本区别直接影响是否能处理重复 Key
Stream 处理 Map 时,Entry 的 Key 很可能重复(比如按 Value 分组后反向映射),但默认 toMap 遇到重复 Key 直接抛 IllegalStateException。
立即学习“Java免费学习笔记(深入)”;
必须根据场景选对重载:
-
toMap(keyMapper, valueMapper):最简,无容错,重复 Key 必崩 -
toMap(keyMapper, valueMapper, mergeFunction):靠第三个函数决定怎么“合并”重复 Key 对应的多个 Value,比如取大值、拼接字符串 -
toMap(keyMapper, valueMapper, mergeFunction, mapFactory):在上一条基础上指定 Map 实现类,如LinkedHashMap::new保序,TreeMap::new自动排序
示例(保留第一个出现的 Value):
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(v1, v2) -> v1 // 后来者让位
));
用 entrySet().stream() 而不是 keySet().stream() 或 values().stream() 才能同时访问 Key 和 Value
想按 Value 过滤、再重建 Map,必须从 entrySet() 开始。如果只用 keySet().stream(),你拿不到 Value,就无法做 filter;如果只用 values().stream(),你根本没 Key,重建 Map 就成了无源之水。
常见错误写法:
// ❌ 错:只有 key,不知道对应 value 是多少
original.keySet().stream()
.filter(k -> original.get(k) > 10) // 多次 get,性能差,且可能 NPE
.collect(Collectors.toMap(k -> k, k -> original.get(k))); // 重复查表
- 每次
original.get(k)都是一次哈希查找,O(1) 但叠加起来没必要 - 如果
original是ConcurrentHashMap,中间被修改还可能引发不一致 - 正确姿势永远是
entrySet().stream()—— 一次拿到键值对,安全又高效
过滤后 Map 类型丢失:原 Map 是 LinkedHashMap,结果却变成 HashMap
Collectors.toMap 默认返回 HashMap,不管你输入的是 TreeMap、LinkedHashMap 还是 EnumMap。顺序、排序、线程安全等特性全丢。
如果你依赖插入顺序(比如 UI 展示需保持配置项顺序),必须显式传入 mapFactory 参数:
- 要保序:
LinkedHashMap::new - 要排序:
TreeMap::new(注意 Key 必须实现Comparable或传Comparator) - 要枚举键专用:
() -> new EnumMap<MyEnum, String>(MyEnum.class)
示例(保持插入顺序):
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue,
(v1, v2) -> v1,
LinkedHashMap::new
));
实际写的时候,最容易被忽略的是 null 安全和类型继承关系——哪怕你确认数据干净,上线后上游一加空值,服务就挂。还有就是以为 filter 写对了,结果用错了 entrySet,白忙一场。










