null造成的NPE(空指针异常)
已初始化的对象和未初始化的对象在调用方法时有本质区别,主要体现在是否会导致 NullPointerException(NPE)。
目录
(2) 使用 Optional 包装可能为 null 的对象(Java 8+)
(4) 使用 Objects.requireNonNull 快速失败
已初始化的对象
特点:对象已通过 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 表示"没有对象",不能调用任何方法
[] 表示"有一个空集合对象",可以安全调用所有方法
实践:始终在声明时初始化集合字段为空的不可变/可变集合
上一篇: 几秒钟就充满电!科学
下一篇: 暂无数据