什么是 NullPointerException,如何修复它?
-
03-07-2019 - |
题
什么是空指针异常(java.lang.NullPointerException
)以及是什么导致了它们?
可以使用哪些方法/工具来确定原因,以便阻止异常导致程序提前终止?
解决方案
当您声明一个引用变量(即一个对象)时,您实际上是在创建一个指向对象的指针。请考虑以下代码,其中声明基本类型 int
:
int x;
x = 10;
在此示例中,变量 x
是 int
,Java会将其初始化为 0
。在第二行分配 10
的值时, 10
的值将写入 x
引用的内存位置。
但是,当您尝试声明引用类型时,会发生不同的事情。请使用以下代码:
Integer num;
num = new Integer(10);
第一行声明了一个名为 num
的变量,但它实际上并不包含原始值。相反,它包含一个指针(因为类型是 Integer
,它是一个引用类型)。由于您尚未说明要指出的内容,因此Java将其设置为 null
,这意味着“我指向 nothing ”。
在第二行中, new
关键字用于实例化(或创建) Integer
类型的对象和指针变量 num
被分配给该 Integer
对象。
当您声明变量但未创建对象时,会发生 NullPointerException
。所以你指的是实际上并不存在的东西。
如果您尝试取消引用 num
,在创建对象之前,您将获得 NullPointerException
。在最琐碎的情况下,编译器将捕获问题并让您知道“ num可能尚未初始化
”,但有时您可能会编写不直接创建对象的代码。
例如,您可以使用以下方法:
public void doSomething(SomeObject obj) {
//do something to obj
}
在这种情况下,您不是创建对象 obj
,而是假设它是在调用 doSomething()
方法之前创建的。注意,可以像这样调用方法:
doSomething(null);
在这种情况下, obj
是 null
。如果该方法旨在对传入的对象执行某些操作,则抛出 NullPointerException
是合适的,因为它是程序员错误,程序员将需要该信息用于调试目的。
doSomething()
可以写成:
/**
* @param obj An optional foo for ____. May be null, in which case
* the result will be ____.
*/
public void doSomething(SomeObject obj) {
if(obj != null) {
//do something
} else {
//do something else
}
}
其他提示
NullPointerException
是当您尝试使用指向内存中没有位置的引用(null)时发生的异常,就好像它引用了一个对象一样。在空引用上调用方法或尝试访问空引用的字段将触发 NullPointerException
。这些是最常见的,但上列出了其他方式。 NullPointerException
javadoc页面。
可能我用来说明 NullPointerException
的最快的示例代码是:
public class Example {
public static void main(String[] args) {
Object obj = null;
obj.hashCode();
}
}
在 main
内的第一行,我明确地将 Object
引用 obj
设置为等于 null
。这意味着我有一个引用,但它没有指向任何对象。之后,我尝试将引用视为通过调用其上的方法指向对象。这会导致 NullPointerException
,因为没有代码可以在引用指向的位置执行。
(这是技术性的,但我认为值得一提的是:指向null的引用与指向无效内存位置的C指针不同。空指针实际上并不指向任何地方,与指向恰好无效的位置略有不同。)
什么是空指针异常?
一个好的起点是 Java文档. 。他们涵盖了这个:
当应用程序尝试使用 null 时引发,如果 object 是必需的。这些包括:
- 调用空对象的实例方法。
- 访问或修改空对象的字段。
- 将 null 的长度视为数组。
- 访问或修改 null 的槽,就像它是一个数组一样。
- 抛出 null 就像它是一个 Throwable 值一样。
应用程序应抛出此类的实例以指示其他 非法使用 null 对象。
还有一种情况是,如果您尝试使用空引用 synchronized
, ,这也会抛出这个异常, 根据 JLS:
SynchronizedStatement: synchronized ( Expression ) Block
- 否则,如果表达式的值为 null,则
NullPointerException
被抛出。
我如何解决它?
所以你有一个 NullPointerException
. 。你如何解决它?让我们举一个简单的例子,它会抛出一个 NullPointerException
:
public class Printer {
private String name;
public void setName(String name) {
this.name = name;
}
public void print() {
printString(name);
}
private void printString(String s) {
System.out.println(s + " (" + s.length() + ")");
}
public static void main(String[] args) {
Printer printer = new Printer();
printer.print();
}
}
识别空值
第一步是准确识别 哪些值导致异常. 。为此,我们需要进行一些调试。学会阅读很重要 堆栈跟踪. 。这将显示抛出异常的位置:
Exception in thread "main" java.lang.NullPointerException
at Printer.printString(Printer.java:13)
at Printer.print(Printer.java:9)
at Printer.main(Printer.java:19)
在这里,我们看到第 13 行抛出了异常(在 printString
方法)。查看该行并检查哪些值为 null
添加 记录语句 或使用 调试器. 。我们发现 s
为空,并调用 length
其上的方法抛出异常。我们可以看到程序停止抛出异常 s.length()
从方法中删除。
追踪这些值的来源
接下来检查这个值来自哪里。通过跟踪该方法的调用者,我们看到 s
传入的是 printString(name)
在里面 print()
方法,以及 this.name
一片空白。
跟踪应设置这些值的位置
哪里 this.name
放?在里面 setName(String)
方法。通过更多的调试,我们可以看到这个方法根本没有被调用。如果调用了该方法,请务必检查 命令 调用这些方法,并且未调用 set 方法 后 打印方法。
这足以给我们一个解决方案:添加一个呼叫 printer.setName()
打电话之前 printer.print()
.
其他修复
该变量可以有一个 默认值 (和 setName
可以防止它被设置为空):
private String name = "";
要么是 print
或者 printString
方法可以 检查是否为空, , 例如:
printString((name == null) ? "" : name);
或者你可以设计这个类,以便 name
始终具有非空值:
public class Printer {
private final String name;
public Printer(String name) {
this.name = Objects.requireNonNull(name);
}
public void print() {
printString(name);
}
private void printString(String s) {
System.out.println(s + " (" + s.length() + ")");
}
public static void main(String[] args) {
Printer printer = new Printer("123");
printer.print();
}
}
也可以看看:
我还是找不到问题
如果您尝试调试问题但仍然没有解决方案,您可以发布问题以获取更多帮助,但请确保包含您迄今为止尝试过的内容。最低限度, 包括堆栈跟踪 在问题中,并且 标记重要的行号 在代码中。另外,首先尝试简化代码(请参阅 SSCCE).
问题:是什么原因导致 NullPointerException
(NPE)?
你应该知道,Java 类型分为 原始类型 (boolean
, int
, 等)和 参考类型. 。Java中的引用类型允许您使用特殊值 null
这是 Java 中“没有对象”的说法。
A NullPointerException
每当您的程序尝试使用 null
就好像它是一个真正的参考一样。例如,如果你这样写:
public class Test {
public static void main(String[] args) {
String foo = null;
int length = foo.length(); // HERE
}
}
标记为“HERE”的语句将尝试运行 length()
上的方法 null
引用,这会抛出一个 NullPointerException
.
您可以通过多种方式使用 null
值将导致 NullPointerException
. 。事实上,你唯一能做的事情 能 做与 null
不引起 NPE 的情况是:
- 将其分配给引用变量或从引用变量中读取它,
- 将其分配给数组元素或从数组元素中读取它(前提是数组引用本身不为空!),
- 将其作为参数传递或将其作为结果返回,或者
- 使用测试它
==
或者!=
运营商,或instanceof
.
问题:如何读取 NPE 堆栈跟踪?
假设我编译并运行上面的程序:
$ javac Test.java
$ java Test
Exception in thread "main" java.lang.NullPointerException
at Test.main(Test.java:4)
$
第一个观察:编译成功!程序中的问题不是编译错误。它是一个 运行 错误。(有些 IDE 可能会警告你的程序将始终抛出异常......但标准 javac
编译器没有。)
第二次观察:当我运行该程序时,它输出两行“gobbledy-gook”。 错误的!! 那不是官样文章。这是一个堆栈跟踪...它提供了 重要信息 如果您花时间仔细阅读,这将帮助您找出代码中的错误。
那么让我们看看它说了什么:
Exception in thread "main" java.lang.NullPointerException
堆栈跟踪的第一行告诉您许多事情:
- 它告诉您抛出异常的 Java 线程的名称。对于一个只有一个线程的简单程序(就像这个),它将是“main”。让我们继续 ...
- 它告诉您抛出的异常的全名;IE。
java.lang.NullPointerException
. - 如果异常有关联的错误消息,则会在异常名称之后输出。
NullPointerException
在这方面很不寻常,因为它很少有错误消息。
第二行是诊断 NPE 时最重要的一行。
at Test.main(Test.java:4)
这告诉我们很多事情:
- “at Test.main”表示我们在
main
的方法Test
班级。 - “Test.java:4”给出了类的源文件名,它告诉我们发生这种情况的语句位于文件的第 4 行。
如果您数一下上面文件中的行数,就会发现第 4 行是我用“HERE”注释标记的行。
请注意,在更复杂的示例中,NPE 堆栈跟踪中会有很多行。但你可以确定第二行(第一个“at”行)会告诉你 NPE 被抛出的位置1.
简而言之,堆栈跟踪将明确告诉我们程序的哪个语句引发了 NPE。
1 - 不完全正确。有一种叫做嵌套异常的东西......
问题:如何查找代码中 NPE 异常的原因?
这是最难的部分。简而言之,就是对堆栈跟踪、源代码和相关 API 文档提供的证据进行逻辑推理。
让我们首先用简单的例子(上面)来说明。我们首先查看堆栈跟踪告诉我们 NPE 发生位置的行:
int length = foo.length(); // HERE
怎么会抛出 NPE?
事实上只有一种方法:它只有在以下情况下才会发生 foo
有价值 null
. 。然后我们尝试运行 length()
方法上 null
和 ....砰!
但是(我听到你说)如果 NPE 被扔进 length()
方法调用?
好吧,如果发生这种情况,堆栈跟踪看起来会有所不同。第一个“at”行表示异常是在 java.lang.String
类,以及第 4 行 Test.java
将是第二个“at”行。
那是哪里做的 null
来自?在这种情况下,这是显而易见的,我们需要采取什么措施来解决它也是显而易见的。(分配一个非空值给 foo
.)
好的,让我们尝试一个稍微棘手的例子。这将需要一些 逻辑演绎.
public class Test {
private static String[] foo = new String[2];
private static int test(String[] bar, int pos) {
return bar[pos].length();
}
public static void main(String[] args) {
int length = test(foo, 1);
}
}
$ javac Test.java
$ java Test
Exception in thread "main" java.lang.NullPointerException
at Test.test(Test.java:6)
at Test.main(Test.java:10)
$
所以现在我们有两条“at”行。第一个是针对这一行的:
return args[pos].length();
第二个是针对这一行的:
int length = test(foo, 1);
看第一行,怎么会抛出 NPE?有两种方法:
- 如果值
bar
是null
然后bar[pos]
会抛出一个 NPE。 - 如果值
bar[pos]
是null
然后打电话length()
上面会抛出一个 NPE。
接下来,我们需要弄清楚哪些场景可以解释实际发生的情况。我们将从探索第一个开始:
哪里有 bar
来自?它是一个参数 test
方法调用,如果我们看看如何 test
被调用,我们可以看到它来自 foo
静态变量。另外,我们可以清楚地看到我们初始化了 foo
为非空值。这足以暂时驳回这种解释。(理论上,其他东西可以 改变 foo
到 null
...但这并没有发生在这里。)
那么我们的第二种情况呢?嗯,我们可以看到 pos
是 1
, ,所以这意味着 foo[1]
必须是 null
. 。那可能吗?
它的确是!这就是问题所在。当我们像这样初始化时:
private static String[] foo = new String[2];
我们分配一个 String[]
有两个元素 被初始化为 null
. 。之后我们就没有再改变过内容了 foo
...所以 foo[1]
仍将是 null
.
就像您正在尝试访问 null
的对象一样。请考虑以下示例:
TypeA objA;
此时您只是声明此对象,但已初始化或已实例化。每当你尝试访问其中的任何属性或方法时,它都会抛出 NullPointerException
,这是有道理的。
同样见下面的例子:
String a = null;
System.out.println(a.toString()); // NullPointerException will be thrown
当应用程序在需要对象的情况下尝试使用null时,抛出空指针异常。其中包括:
- 调用
null
对象的实例方法。 - 访问或修改
null
对象的字段。 - 将
null
的长度视为数组。 - 访问或修改
null
的插槽,就像它是一个数组一样。 - 抛出
null
,好像它是一个Throwable值。
醇>
应用程序应抛出此类的实例以指示 null
对象的其他非法用法。
参考: http://docs.oracle。 COM / JavaSE的/ 8 /文档/ API /爪哇/郎/ NullPointerException.html
null
指针是指向无处的指针。当您取消引用指针 p
时,您说“在p”中存储的位置给我数据。当 p
是 null
指针时,存储在 p
中的位置是 nowhere
,你说的是“给”我在“无处”的位置的数据。显然,它不能这样做,因此抛出空指针异常
。
一般情况下,这是因为某些事情尚未正确初始化。
已经有很多解释来解释它是如何发生的以及如何解决它,但你也应该遵循最佳实践来避免 NullPointerException
。
另见: 一个好的清单最佳做法
我想补充一点,非常重要的是,要好好利用 final
修饰符。
使用“最终”版本。适用于Java的修饰符
<强>要点:强>
- 使用
final
修饰符强制执行良好的初始化。 - 避免在方法中返回null,例如在适用时返回空集合。
- 使用注释
@NotNull
和@Nullable
- 快速失败并使用断言来避免在不应为null的情况下在整个应用程序中传播空对象。
- 首先使用等于已知对象:
if(&quot; knownObject&quot; .equals(unknownObject)
- 首选
valueOf()
over toString()。 - 使用null safe
StringUtils
方法StringUtils.isEmpty(null)
。
醇>
空指针异常是指在没有初始化的情况下使用对象的指示符。
例如,下面是一个将在我们的代码中使用它的学生班。
public class Student {
private int id;
public int getId() {
return this.id;
}
public setId(int newId) {
this.id = newId;
}
}
以下代码为您提供空指针异常。
public class School {
Student student;
public School() {
try {
student.getId();
}
catch(Exception e) {
System.out.println("Null pointer exception");
}
}
}
因为您正在使用 student
,但是您忘了将其初始化为
正确的代码如下所示:
public class School {
Student student;
public School() {
try {
student = new Student();
student.setId(12);
student.getId();
}
catch(Exception e) {
System.out.println("Null pointer exception");
}
}
}
在Java中,一切(不包括原始类型)都是类的形式。
如果你想使用任何对象,那么你有两个阶段:
- 宣布
- 初始化
例子:
- 宣言:
Object object;
- 初始化:
object = new Object();
数组概念相同:
- 宣言:
Item item[] = new Item[5];
- 初始化:
item[0] = new Item();
如果您没有给出初始化部分,那么 NullPointerException
出现。
在 Java 中,您声明的所有变量实际上都是“引用”。对象(或基元)而不是对象本身。
当您尝试执行一个对象方法时,引用会要求生命对象执行该方法。但是如果引用引用NULL(没有,零,无效,nada),则该方法无法执行。然后运行时通过抛出NullPointerException来告诉您。
您的引用是“指向”为null,因此“Null - &gt;指针&QUOT;
该对象存在于VM内存空间中,访问它的唯一方法是使用 this
引用。举个例子:
public class Some {
private int id;
public int getId(){
return this.id;
}
public setId( int newId ) {
this.id = newId;
}
}
在代码中的其他位置:
Some reference = new Some(); // Point to a new object of type Some()
Some otherReference = null; // Initiallly this points to NULL
reference.setId( 1 ); // Execute setId method, now private var id is 1
System.out.println( reference.getId() ); // Prints 1 to the console
otherReference = reference // Now they both point to the only object.
reference = null; // "reference" now point to null.
// But "otherReference" still point to the "real" object so this print 1 too...
System.out.println( otherReference.getId() );
// Guess what will happen
System.out.println( reference.getId() ); // :S Throws NullPointerException because "reference" is pointing to NULL remember...
这一点很重要 - 当没有更多对象的引用时(在上面的例子中 reference
和 otherReference
都指向null)那么对象是“无法到达”。我们无法使用它,所以这个对象已准备好被垃圾收集,并且在某些时候,VM将释放该对象使用的内存并分配另一个。
当一个声明一个对象数组,然后立即尝试取消引用它内部的元素时,会发生另一个 NullPointerException
。
String[] phrases = new String[10];
String keyPhrase = "Bird";
for(String phrase : phrases) {
System.out.println(phrase.equals(keyPhrase));
}
如果比较顺序颠倒,可以避免这种特殊的NPE;即,在保证的非空对象上使用 .equals
。
数组中的所有元素初始化为其共同初始值;对于任何类型的对象数组,这意味着所有元素都是 null
。
您必须在访问或取消引用之前初始化数组中的元素。
String[] phrases = new String[] {"The bird", "A bird", "My bird", "Bird"};
String keyPhrase = "Bird";
for(String phrase : phrases) {
System.out.println(phrase.equals(keyPhrase));
}