源本科技 | 码上会

Java 空指针异常

2026/01/23
34
0

学习目标

  • 理解 NullPointerException 的成因与典型触发场景

  • 掌握避免空指针异常的常用编程技巧

  • 学会使用 Optional 类等现代 Java 特性安全处理可能为 null 的值

  • 能在实际开发中设计健壮的参数校验与空值防护逻辑


NullPointerException

NullPointerException(简称 NPE)是 Java 中最常见的运行时异常之一,属于 RuntimeException 的子类。它在程序试图使用一个值为 null 的对象引用时抛出。在 Java 中,null 是一个特殊字面量,表示“没有对象”或“引用未指向任何实例”。虽然语法上合法,但对 null 引用执行操作会导致程序崩溃。

常见触发场景

以下操作若作用于 null 引用,将抛出 NullPointerException

  • 调用 null 对象的方法(如 str.length()

  • 访问或修改 null 对象的字段

  • 获取 null 数组的 length 属性

  • 访问或赋值 null 数组的元素

  • null 作为 Throwable 抛出(如 throw null;

  • synchronized 块中使用 null 对象作为锁


示例:基础 NPE 触发

public class Example {
    public static void main(String[] args) {
        String s = null;
        System.out.println(s.length()); // 尝试调用 null 对象的方法
    }
}
Exception in thread "main" java.lang.NullPointerException
    at Example.main(Example.java:5)

此处 s 未指向任何 String 实例,调用 length() 时 JVM 无法找到对应方法,故抛出 NPE。


为什么允许 null

尽管 null 容易引发错误,但它在设计上有其用途:

  • 表示“无值”状态:如链表尾节点、树的叶子子节点

  • 支持设计模式:例如 Null Object Pattern(空对象模式)、Singleton(单例模式中延迟初始化)

  • API 兼容性:许多历史 API 使用 null 表示缺失或默认行为

然而,过度依赖 null 会降低代码健壮性,因此现代 Java 编程更推荐显式处理“可能缺失”的语义。


如何避免 NPE

1. 字符串比较

将字面量放在 .equals() 左侧

错误做法:

String input = null;
if (input.equals("hello")) { ... } // NPE!

正确做法:

String input = null;
if ("hello".equals(input)) { ... } // 安全!返回 false

因为字符串字面量 "hello" 永远不为 null,其 equals() 方法内部会先检查参数是否为 null,从而避免异常。


2. 方法参数校验

提前拒绝 null 输入

在方法入口处主动检查参数,防止后续逻辑因 null 崩溃:

public static int getLength(String s) {
    if (s == null) {
        throw new IllegalArgumentException("参数 s 不能为 null");
    }
    return s.length();
}

优势:

  • 错误信息明确,便于调用方修复

  • 避免在深层逻辑中因 null 导致难以追踪的 NPE


3. 三元运算符进行空值保护

通过条件表达式提前处理 null 情况:

String text = null;
String prefix = (text == null) ? "" : text.substring(0, 5);
System.out.println(prefix); // 输出空字符串,而非抛异常

适用于简单场景,但不宜嵌套过深,否则影响可读性。


4. 使用 Optional

Optional<T> 是 Java 8 引入的容器类,用于显式表达“值可能存在也可能不存在”的语义,强制开发者处理缺失情况。

基本用法示例:

import java.util.Optional;

public class OptionalExample {
    public static void main(String[] args) {
        Optional<String> name = Optional.ofNullable(null);
        
        // 提供默认值
        System.out.println(name.orElse("默认名称")); // 输出:默认名称
        
        // 或使用 orElseGet 提供懒加载默认值
        System.out.println(name.orElseGet(() -> "动态默认值"));
        
        // 安全执行操作
        name.ifPresent(n -> System.out.println("姓名: " + n)); // 不执行,因值为空
    }
}

核心方法:

方法

说明

Optional.of(T)

创建包含非 null 值的 Optional,若传入 null 则立即抛 NPE

Optional.ofNullable(T)

安全包装可能为 null 的值

orElse(T)

若无值,返回指定默认值

orElseGet(Supplier)

若无值,通过函数生成默认值(延迟计算)

ifPresent(Consumer)

仅当有值时执行操作

map(Function) / flatMap(Function)

链式转换,自动跳过 null

最佳实践:在 API 返回可能为 null 的对象时,优先返回 Optional<T>,迫使调用方显式处理空值。


防御性编程建议

  • 初始化引用:声明对象时尽量赋予有效初始值(如空集合、空字符串)

  • 使用注解:借助 @Nullable / @NonNull(如 JetBrains、JSR-305)辅助静态分析工具检测潜在 NPE

  • 单元测试覆盖 null 路径:确保边界情况被验证

  • 日志记录上下文:在捕获 NPE 时记录相关变量状态,便于排查


思考题

  1. 为什么 Optional.of(null) 会抛出 NullPointerException,而 Optional.ofNullable(null) 不会?这体现了什么设计思想?

  2. 在团队协作中,如果某个方法返回 null 表示“未找到”,你认为应改为返回 Optional.empty() 吗?为什么?

  3. 除了上述方法,你还能想到哪些工程化手段(如 Lombok、静态分析工具)来减少 NPE 的发生?