关键是要识别可能为null的隐式拆箱位置并预防NPE,如int x = integerObj、if(booleanFlag)、a+b(含Integer)、map.get()赋值给int、list.get()参与计算等;需用-Xlint:all、IDE空值检查、SpotBugs/ErrorProne等工具拦截,并改用Objects.requireNonNullElse、Boolean.TRUE.equals等安全写法,从数据库、JSON、RPC、DTO源头控制null。

关键不是“检查代码有没有装箱拆箱”,而是识别哪些地方会悄悄触发拆箱、又恰好可能为 null。真正危险的从来不是编译报错,而是运行时突然炸出 NullPointerException,而且只在特定数据下才发生。
盯紧所有隐式拆箱发生的位置
自动拆箱本质是调用 xxxValue() 方法,只要对象是 null,立刻抛异常。以下写法看着合法,实则高危:
-
int x = integerObj;—— 最典型,integerObj为null就崩 -
if (booleanFlag) { ... }—— 触发booleanFlag.booleanValue() -
int sum = a + b;(其中a或b是Integer)—— 加法前先拆箱 -
map.get("key")直接赋给int变量 —— 数据库/缓存未命中返回null,一取就炸 -
list.get(i)在 for-each 或 stream 中参与计算 —— 遍历含null元素的List会立即失败
用静态分析工具提前拦截
人工肉眼很难覆盖所有边界路径,推荐组合使用这些手段:
- 启用
-Xlint:all编译参数,它会警告“潜在的空指针拆箱”(如 JDK 17+ 对if (flag)的提示) - 在 IDE 中开启 “Nullability checks”(IntelliJ 默认开,Eclipse 需配置),对包装类字段标
@Nullable后,赋值给基本类型会标黄警告 - 接入
SpotBugs或ErrorProne,规则如NP_UNWRITTEN_FIELD、BOXED_PRIMITIVE_NULL_CHECK能捕获多数隐性风险 - MyBatis / JPA 实体字段若映射可空数据库列,用 Lombok 的
@Getter(onMethod_ = @NonNull)或自定义 getter 做判空兜底
重构高频风险模式
有些写法看似简洁,实则埋雷多年。替换为更明确、更安全的等价形式:
- 别写:
int val = map.get("key");
改写:int val = Objects.requireNonNullElse(map.get("key"), 0); - 别写:
if (statusFlag) { ... }
改写:if (Boolean.TRUE.equals(statusFlag)) { ... }或if (Objects.nonNull(statusFlag) && statusFlag) - 别写:
sum += list.get(i);
改写:Integer item = list.get(i); if (item != null) sum += item; - 三目运算中混用基本类型与包装类?例如
int x = flag ? obj.getValue() : 0;—— 若getValue()返回null,JDK 8u40 以前版本仍会先尝试拆箱。统一改为int x = flag ? Objects.requireNonNullElse(obj.getValue(), 0) : 0;
从源头控制 null 流入
与其在每处拆箱前加判断,不如让 null 少出现:
- 数据库查询加
COALESCE(amount, 0),避免数值字段返回null - JSON 反序列化时,用 Jackson 的
@JsonSetter(nulls = Nulls.SKIP)或默认值注解@JsonProperty(defaultValue = "0") - RPC 接口返回值约定:数值字段不返回
null,缺失时传 0 或 -1 等业务语义值 - DTO 构造时,对包装类字段显式初始化:
private Integer count = 0;(注意不是= null)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











