Java集合对null支持不统一:HashMap等宽松派允许null,TreeMap等严格派因排序或并发歧义禁止null;Stream需filter或Optional处理null;推荐用emptyList、Optional、枚举替代null。

Java集合对null的支持完全不统一,能不能存、在哪存、存了怎么取,全看具体实现类——不是“语言规定”,而是每个类自己决定的。
哪些集合允许null?关键看底层机制
HashMap、ArrayList、HashSet这些“宽松派”允许null,是因为它们不依赖比较或并发安全逻辑;TreeMap、ConcurrentHashMap、Hashtable这些“严格派”禁止null,是因为null会破坏排序语义或导致get()歧义(比如get(key)返回null,你分不清是key不存在,还是value真就是null)。
-
HashMap和LinkedHashMap:允许一个null键 + 任意数量null值 -
TreeMap:null键直接抛NullPointerException(compareTo()调用失败),null值可以 -
ConcurrentHashMap:put(null, ...)或put(..., null)立刻抛异常 -
ArrayList/LinkedList:支持add(null)、set(i, null),但get(i)返回null时需区分“存的就是null”和“索引越界”(后者抛IndexOutOfBoundsException,不是NPE) -
ArrayDeque:不允许null,否则poll()等方法无法判断“队列空”还是“取到null”
Stream里map遇到null,4种安全写法
流操作中map()若直接调用String::toUpperCase这类方法,遇到null就崩。必须提前过滤或封装。
- 最常用:先
filter(Objects::nonNull)再map(),适合丢弃null元素 - 要兜底:用
map(Optional::ofNullable).map(opt -> opt.orElse("default")),把null转成默认值 - 简单替换:三元运算符
map(s -> s == null ? "N/A" : s.toUpperCase()),语义直白但不够函数式 - 防御性包装:自定义工具方法
safeMap(s -> s.trim(), data),内部判空再执行
Listdata = Arrays.asList("hello", null, "world"); List result = data.stream() .filter(Objects::nonNull) .map(String::toUpperCase) .collect(Collectors.toList()); // ["HELLO", "WORLD"]
别依赖null语义,用更明确的替代方案
靠null表示“无值”在协作和演进中极易出错——别人不知道你存null是故意的还是忘了初始化,升级JDK或换集合实现也可能突然报错。
立即学习“Java免费学习笔记(深入)”;
- 返回集合的方法,统一用
Collections.emptyList()代替null - 单个可能为空的值,优先用
Optional.ofNullable(value)包装,强制调用方处理分支 - 参数校验用
Objects.requireNonNull(param, "param must not be null"),比事后NPE好定位 - 业务上需要“空状态”,考虑定义枚举(如
Status.MISSING)或空对象(EmptyUser.INSTANCE),而非null
最易被忽略的一点:ConcurrentHashMap禁止null不是为了“严谨”,而是因为并发下get()返回null无法区分“key不存在”和“value为null”——这个设计约束在分布式缓存或高并发计数场景中会直接暴露,不是测试能轻易覆盖的边界。










