Java中无法彻底消灭NullPointerException,但可通过Optional封装返回值可选性、@NonNull/@Nullable注解配合静态检查、Objects.requireNonNull主动校验、以及空集合/字符串的防御性判空等手段大幅降低其发生概率。

Java里无法彻底消灭 NullPointerException,但能大幅降低它在运行时突然爆发的概率——关键不在“捕获”,而在“预防”和“显式表达可空性”。
用 Optional 替代 null 返回值(但别滥用)
Optional 不是万能解药,它的设计初衷是封装**方法返回值的可选性**,不是用来包装字段、参数或集合元素。
- ✅ 正确场景:
findUserById(Long id)返回Optional,调用方必须显式处理“找不到”的情况 - ❌ 错误用法:把
user.getName()改成Optional.ofNullable(user).map(User::getName).orElse(null)—— 这只是把 null 搬来搬去,没解决根本问题 - ⚠️ 注意:
Optional.get()仍会抛NoSuchElementException;优先用ifPresent()、orElse()、orElseThrow()
@NonNull 和 @Nullable 注解配合编译检查
注解本身不运行,但配合 IDE(如 IntelliJ)或静态分析工具(如 SpotBugs、ErrorProne),能在编码阶段标出潜在空指针路径。
- 给参数加
@NonNull:IDE 会在调用处提示“传入 null 可能导致 NPE” - 给返回值加
@Nullable:强制调用方做非空判断,比如String getName()标为@Nullable后,name.length()会触发警告 - 推荐用 JetBrains 的
org.jetbrains.annotations.NonNull或 JSR-305(已归档但广泛兼容),避免用javax.annotation(JDK 9+ 默认移除)
用 Objects.requireNonNull() 主动失败,而非静默崩溃
延迟到运行时才发现空指针,不如在入口处立刻暴露问题。这比靠日志堆栈反推更高效。
立即学习“Java免费学习笔记(深入)”;
- 构造函数中校验必填依赖:
public Service(Repository repo) { this.repo = Objects.requireNonNull(repo, "repo must not be null"); } - 方法参数校验:
public void process(@NonNull String input)配合注解 + 编译检查,比手写if (input == null) throw ...更简洁 - 注意:
requireNonNull抛的是NullPointerException,不是IllegalArgumentException—— 这符合语义:你传了个本不该为 null 的引用
集合与字符串的防御性操作要具体
空集合、空字符串、null 字符串三者行为完全不同,混用 == null 判断会埋坑。
- 判空集合优先用
CollectionUtils.isEmpty()(Apache Commons)或list == null || list.isEmpty(),别只看list.size() == 0 - 字符串判空统一用
StringUtils.isBlank(str)(处理 null、""、" "),不用str == null || str.trim().length() == 0 - 避免链式调用前不校验:
user.getAddress().getCity().toUpperCase()→ 应拆成明确步骤,或用 Optional 封装中间层
最易被忽略的一点:第三方 SDK 返回的字段是否可空,文档常写得模糊。与其猜,不如直接在测试里 mock 一个 null 值跑一遍,看它到底抛不抛 NPE —— 真实行为永远比 Javadoc 可靠。










