防止 NullPointerException
导航 异常 主题: ) |
NullPointerException
是一个 RuntimeException
。在 Java 中,一个特殊的 null
可以被分配给一个对象引用。当应用程序尝试使用对象引用时,NullPointerException
会被抛出,该对象引用具有 null
值。这些包括
- 在 null 引用所引用的对象上调用实例方法。
- 访问或修改 null 引用所引用的对象的实例字段。
- 如果引用类型是数组类型,则获取 null 引用的长度。
- 如果引用类型是数组类型,则访问或修改 null 引用的槽。
- 如果引用类型是
Throwable
的子类型,则抛出 null 引用。
应用程序应抛出此类的实例来指示对 null 对象的其他非法使用。
代码部分 6.13:空指针。
Object obj = null;
obj.toString(); // This statement will throw a NullPointerException
|
上面的代码展示了 Java 的一个陷阱,也是最常见的错误来源。没有创建对象,编译器也无法检测到它。NullPointerException
是 Java 中最常见的异常之一。
为什么我们需要 null?
[edit | edit source]我们需要它的原因是,很多时候,我们需要在对象本身创建之前创建对象引用。对象引用不能没有值而存在,所以我们为它分配 null
值。
代码部分 6.14:未实例化的声明对象。
public Person getPerson(boolean isWoman) {
Person person = null;
if (isWoman) {
person = createWoman();
} else {
person = createMan();
}
return person;
}
|
在 代码部分 6.14 中,我们希望在 if-else 中创建 Person
,但我们也希望将对象引用返回给调用者,所以我们需要在 if-else 外部创建对象引用,因为 Java 中的 作用域规则。错误的错误处理和糟糕的契约设计可能是任何编程语言的陷阱。这对 Java 来说也是如此。
现在我们将描述如何防止 NullPointerException
。它没有描述你应该如何编写 Java 的一般技术。这有点用处,可以让你更加了解 null 值,并更加小心地自己生成它们。
这个列表并不完整——没有规则可以完全防止 Java 中的 NullPointerException
,因为标准库必须使用,它们会导致 NullPointerException
。此外,可以观察到 Java 中未初始化的 final 字段,因此你甚至不能在对象创建期间完全信任 final 字段。
一个好的方法是先学习如何处理 NullPointerException
,并精通它。这些建议将帮助你减少 NullPointerException
的出现,但它们不能替代你需要了解 NullPointerException
的需要。
将字符串变量与字符串字面量进行比较
[edit | edit source]当您将变量与字符串字面量进行比较时,大多数人会以这种方式进行
代码部分 6.15:错误的比较。
if (state.equals("OK")) {
...
}
|
始终将字符串字面量放在首位
代码部分 6.16:更好的比较。
if ("OK".equals(state)) {
...
}
|
如果 state
变量为 null,则第一个示例中会得到 NullPointerException
,而第二个示例中则不会。
最大限度地减少在赋值语句中使用关键字 'null'
[edit | edit source]这意味着不要做以下事情
代码部分 6.17:声明异常。
String s = null;
while (something) {
if (something2) {
s = "yep";
}
}
if (s != null) {
something3(s);
}
|
您可以用以下代码替换它
代码部分 6.18:声明异常。
boolean done = false;
while (!done && something) {
if (something2) {
done = true;
something3("yep");
}
}
|
您也可以考虑在第一个示例中用 "" 替换 null,但默认值会导致由于默认值遗留而产生的错误。实际上,NullPointerException
更好,因为它允许运行时通知您错误,而不是只使用默认值继续执行。
最大限度地减少使用 new Type[int] 语法创建对象数组
[edit | edit source]使用 new Object[10]
创建的数组有 10 个 null 指针。这比我们想要的要多 10 个,所以使用集合代替,或者在初始化时用以下代码显式填充数组
代码部分 6.19:声明异常。
Object[] objects = {"blah", 5, new File("/usr/bin")};
|
或
代码部分 6.20:声明异常。
Object[] objects;
objects = new Object[]{"blah", 5, new File("/usr/bin")};
|
检查从 '不可信' 方法获取的所有引用
[edit | edit source]许多可以返回引用的方法可以返回 null 引用。确保您检查这些方法。例如
代码部分 6.21:声明异常。
File file = new File("/etc");
File[] files = file.listFiles();
if (files != null) {
stuff
}
|
如果 /etc
不是目录,则 File.listFiles()
可能返回 null。
如果您愿意,您可以决定信任某些方法不会返回 null,但这是一种假设。一些没有指定它们可能返回 null 的方法实际上返回了 null,而不是抛出异常。
foreach 循环陷阱
[edit | edit source]如果您在 foreach 循环中循环数组或集合,请小心。
代码部分 6.22:访问集合。
Collection<Integer> myNumbers = buildNumbers();
for (Integer myNumber : myNumbers) {
System.out.println(myNumber);
}
|
如果对象为空,它不会只执行零次循环,而是会抛出空指针异常。所以不要忘记这种情况。添加一个 `if` 语句或返回空集合。
代码部分 6.23:访问集合的安全性。
Collection<Integer> myNumbers = buildNumbers();
if (myNumbers != null) {
for (Integer myNumber : myNumbers) {
System.out.println(myNumber);
}
}
|
有一些工具,比如 FindBugs,可以解析你的代码并警告你潜在的错误。大多数情况下,它可以检测可能的空指针。