null造成的NPE(空指针异常)

已初始化的对象和未初始化的对象在调用方法时有本质区别,主要体现在是否会导致 NullPointerException(NPE)。

目录

已初始化的对象

未初始化的对象

总结

如何避免未初始化导致的 NPE?

(1)显式初始化对象

(2) 使用 Optional 包装可能为 null 的对象(Java 8+)

(3) 防御性判空

(4) 使用 Objects.requireNonNull 快速失败

特殊情况:

部分初始化的对象

自动拆箱时的 null

数组或集合中的 null 

访问null元素

null 和空列表 [] 的关键区别

空列表优于 null

总结


已初始化的对象

特点:对象已通过 new 创建,内存已分配,可以安全调用方法。

示例:

String str = new String("Hello"); // 已初始化
System.out.println(str.length()); // 安全调用,输出 5

关键点:

  • 对象引用指向堆内存中的实际实例。
  • 可以调用该对象的所有公共方法和访问公共字段。
  • 不会引发 NullPointerException。

未初始化的对象

特点:对象引用为 null,未指向任何实例,调用方法会抛出 NullPointerException。
示例

String str = null; // 未初始化
System.out.println(str.length()); // 抛出 NullPointerException

关键点:

  • 对象引用未分配内存(值为 null)。
  • 任何尝试调用方法或访问字段的操作都会立即触发 NullPointerException。

        常见原因:

  •         忘记调用 new 初始化。
  •         方法返回 null 未被处理。
  •         对象被显式赋值为 null。

总结

场景 已初始化对象 未初始化对象(null)
内存状态 堆内存中存在实例 引用指向 null
调用方法 正常执行 抛出 NullPointerException
访问字段 正常访问 抛出 NullPointerException
典型代码 obj.method()(安全) null.method()(崩溃)

如何避免未初始化导致的 NPE?

(1)显式初始化对象

// 正确做法
List<String> list = new ArrayList<>(); // 已初始化
list.add("Item"); // 安全操作

(2) 使用 Optional 包装可能为 null 的对象(Java 8+)

Optional<String> optionalStr = Optional.ofNullable(getNullableString());
optionalStr.ifPresent(s -> System.out.println(s.length())); // 安全调用

(3) 防御性判空

String str = getPossibleNullString();
if (str != null) { // 显式检查
    System.out.println(str.length());
}

(4) 使用 Objects.requireNonNull 快速失败

public void process(String input) {
    Objects.requireNonNull(input, "input 不能为 null"); // 若 input=null 则立即抛 NPE
    System.out.println(input.length());
}

特殊情况:

部分初始化的对象

某些情况下,对象虽然被 new 创建,但内部状态未完全初始化,也可能导致异常。

class User {
    private String name;
    // 忘记初始化 name
    public String getNameUpperCase() {
        return name.toUpperCase(); // 可能抛出 NPE(若 name 未被赋值)
    }
}

User user = new User(); // 对象已 new,但 name=null
System.out.println(user.getNameUpperCase()); // NPE!

解决方法

  • 在构造函数或初始化块中确保所有必要字段被赋值。
  • 使用默认值(如 private String name = "";)。

自动拆箱时的 null

Integer num = null;
int value = num; // 抛出 NPE(自动拆箱调用 num.intValue())

数组或集合中的 null 

访问null元素

String[] arr = new String[3];
System.out.println(arr[0].toUpperCase()); // 抛出 NPE(arr[0] 是 null)

null 和空列表 [] 的关键区别

特性 null new ArrayList<>() (空列表 [])
内存状态 未分配内存的引用 已初始化的空集合对象
调用方法 任何方法调用会抛 NullPointerException 可以安全调用所有方法(如 add()size()
语义 "没有集合" "有一个集合,但它是空的"

举例:

反例:

class Node {
    List<Node> children;  // 未初始化,默认是 null
    
    void addChild(Node child) {
        children.add(child);  // 第一次调用会抛 NullPointerException!
    }
}

正例:

class Node {
    List<Node> children = new ArrayList<>();  // 初始化为空列表
    
    void addChild(Node child) {
        children.add(child);  // 永远安全
    }
}

实际场景对比:

直接操作未初始化的列表

Node node = new Node();
node.getChildren().size();  // 出错 NullPointerException

操作已初始化的空列表

Node node = new Node();
node.getChildren().size();  //  返回 0
node.getChildren().add(new Node());  //  正常添加

空列表优于 null

  • 防御性编程:避免无处不在的 if (children != null) 判空
  • 代码简洁性:所有方法可以直接操作列表
  • API 友好性:调用方不需要处理 null 的特殊情况
  • 序列化友好:JSON 等序列化工具会生成 [] 而非 null

  • Collections.emptyList() 返回空列表而非 null
  • new HashMap() 初始化空映射而非 null
  • String 的 "" 优于 null

总结

null 表示"没有对象",不能调用任何方法

[] 表示"有一个空集合对象",可以安全调用所有方法

实践:始终在声明时初始化集合字段为空的不可变/可变集合