【Java杂项】Java 中的 null:空指针、自动拆箱与集合边界详解

发布时间:2026/6/11 16:16:15

【Java杂项】Java 中的 null:空指针、自动拆箱与集合边界详解 【Java杂项】Java 中的 null空指针、自动拆箱与集合边界详解一、先把 null、空、缺失分开二、最常见的 NPE 来自自动拆箱三、Map.get() 的 null 不一定表示没值四、TreeMap 为什么默认不接受 null key五、工程上怎么防 null5.1 入参先校验5.2 返回空集合不要返回 null5.3 Optional 只负责“可能没有”别把它当装饰品5.4 Stream 里先过滤再收集5.5 静态分析和注解别省六、总结 博主名称超级苦力怕 个人专栏《基本功修炼大全》 每一次思考都是突破的前奏每一次复盘都是精进的开始文章元信息适合读者正在学习 Java 基础、异常处理和集合框架或准备 Java 面试、排查空指针问题的读者前置知识了解 Java 引用类型、基本类型与包装类、Map 和 List 的基本用法本文用最小代码讲清 Java 中 null 为什么容易引发空指针并继续展开自动拆箱、Map.get 返回 null、TreeMap 排序边界和工程防御写法。一、先把null、空、缺失分开null、空集合、业务缺失不是一回事。很多坑都是因为这三者被混着用了。状态含义常见表达null没有对象引用String s null;空对象存在但内容为空new ArrayList()缺失业务上没有这个值Optional.empty()再看常见容器对null的态度容器null规则说明ArrayList可以存多个null只是元素值为空不影响列表结构LinkedList可以存多个null但作为队列用时要小心语义混淆HashSet可以有一个null元素底层依赖HashMapHashMap一个nullkey多个nullvalueget()返回null不能直接代表“没这个 key”TreeMapnullvalue 可以nullkey 默认不行需要比较 key 来维持有序树ConcurrentHashMap不允许nullkey / value避免并发场景的语义歧义Hashtable不允许nullkey / value老实现规则更严格ArrayDeque/PriorityQueue不允许null需要用null作为空或异常状态的边界信号一句话记忆容器能不能放null和它是否要拿null当“空信号”是两回事。二、最常见的 NPE 来自自动拆箱null本身不会自动变成0、false或空字符串。只要把包装类型交给基本类型编译器就会偷偷拆箱。示例包装类型自动拆箱触发 NPEIntegercountnull;inttotalcount;// NPEBooleanreadynull;if(ready){// NPESystem.out.println(ok);}编译器背后大致会变成等价展开编译器会调用xxxValue()inttotalcount.intValue();if(ready.booleanValue()){System.out.println(ok);}再看一个更隐蔽的场景示例Map.get()返回null后赋给intMapString,IntegerscoreMapnewHashMap();intscorescoreMap.get(Tom);// NPE这里get(Tom)返回的是null但左边是int于是又发生了拆箱。写法触发点结果int x integer;自动拆箱Integer.intValue()调到nullif (booleanObj)条件判断拆箱Boolean.booleanValue()调到nullint x flag ? integer : 0;三目运算符统一类型可能先选出Integer再拆箱sum integer;运算前拆箱null直接炸掉核心结论只要包装类型要参与运算、比较或条件判断就先想一遍“这里会不会被拆箱”。三、Map.get()的null不一定表示没值Map.get()返回null有三种可能key 不存在。key 存在但 value 本身就是null。对于可变 keykey 的哈希身份已经被改坏了。前两种可以这样区分示例用containsKey()区分 key 是否存在MapString,IntegermapnewHashMap();map.put(A,null);System.out.println(map.get(A));// nullSystem.out.println(map.containsKey(A));// true所以如果业务上允许nullvalueget()的结果就不能单独说明问题得配合containsKey()。如果业务上不希望出现这个歧义更稳的做法是不存nullvalue。返回空集合而不是null。用Optional表达“可能没有值”。四、TreeMap为什么默认不接受nullkeyTreeMap的 key 要参与排序。默认自然排序下key 需要能比较null没法比较所以会抛NullPointerException。示例默认TreeMap不接受nullkeyTreeMapString,IntegermapnewTreeMap();map.put(A,1);map.put(null,2);// NPE如果确实有业务需求可以提供能处理null的比较器示例自定义比较器显式处理nullTreeMapString,IntegermapnewTreeMap((a,b)-{if(ab){return0;}if(anull){return-1;}if(bnull){return1;}returna.compareTo(b);});map.put(null,2);map.put(A,1);System.out.println(map.get(null));这里要注意两点点说明比较器要稳定compare(a, b)不能前后乱跳否则put()/get()会不一致compare(a, b) 0的语义要清楚不然 TreeMap 可能把两个不同对象当成同一个 keyTreeSet的规则也一样因为它底层也是TreeMap。五、工程上怎么防null5.1 入参先校验公共方法和构造函数里能在入口挡住的null就别放进去。示例在构造函数入口校验非空参数publicUser(Stringname,Integerage){this.nameObjects.requireNonNull(name,name);this.ageObjects.requireNonNull(age,age);}5.2 返回空集合不要返回null示例返回空集合而不是nullpublicListStringlistTags(){returnCollections.emptyList();}这样调用方就不用先判空再遍历。5.3Optional只负责“可能没有”别把它当装饰品示例根据是否确定非空选择of()或ofNullable()Optional.of(value);// 已知非空Optional.ofNullable(val);// 不确定是否为空of()适合已知不为空的值ofNullable()适合边界输入。5.4 Stream 里先过滤再收集findFirst()、max()、min()、toMap()、groupingBy()这类操作都要小心null。API注意点findFirst()/max()/min()流里不要混入null元素Collectors.toMap()value mapper 不要返回nullCollectors.groupingBy()分组 key 不要返回null常见做法是先过滤示例Stream 收集前先过滤null元素stream.filter(Objects::nonNull).collect(Collectors.toList());5.5 静态分析和注解别省Nullable、NotNull这类注解配合静态分析工具能把很多 NPE 提前拦下来。六、总结问题结论null能不能直接参与运算不能常见结果是自动拆箱 NPEMap.get()返回null就一定没值吗不一定可能是 value 本身就是nullTreeMap为什么默认不收nullkey因为 key 要参与排序默认自然顺序无法比较null哪些容器最容易和null打架Map、Set、TreeMap、Queue、包装类型工程上最稳的做法入口校验、空集合返回、Optional、显式判空核心结论null不是一个统一的“空值”。先分清它在当前场景里代表“缺失”“空对象”还是“非法输入”后面的代码才不会一路埋雷。

相关新闻