什么是空指针异常(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
,这意味着“我没有指向任何东西”。
在第二行中,new
关键字用于实例化(或创建)一个integer
类型的对象,并将指针变量num
分配给该integer
对象。
NullPointerException
发生在您声明了一个变量,但在尝试使用该变量的内容(称为取消引用)之前没有创建对象并赋值给该变量。 所以你指向的是一个实际上并不存在的东西。
取消引用通常发生在使用.
访问方法或字段,或使用[
索引数组时。
如果在创建对象之前尝试取消引用num
,则会得到一个NullPointerException
。 在最琐碎的情况下,编译器会捕捉到问题并让您知道“num可能尚未初始化
”,但有时您可能会编写不直接创建对象的代码。
例如,您可以使用如下方法:
public void doSomething(SomeObject obj) {
//do something to obj
}
在这种情况下,您不会创建对象obj
,而是假设它是在调用doSomething()
方法之前创建的。 注意,可以这样调用方法:
doSomething(null);
在这种情况下,obj
是null
。 如果该方法旨在对传入的对象执行某些操作,则抛出NullPointerException
是合适的,因为这是程序员错误,程序员需要该信息进行调试。 请在异常消息中包含对象变量的名称,如
Objects.requireNonNull(a, "a");
或者,可能存在这样的情况,即方法的目的不仅仅是对传入的对象进行操作,因此空参数可能是可以接受的。 在这种情况下,您将需要检查空参数,并采取不同的行为。 您还应该在文档中解释这一点。 例如,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
}
}
最后,如何找出异常&; 使用堆栈跟踪的原因
可以使用什么方法/工具来确定原因,以便您停止异常导致程序过早终止?
带有findbugs的声纳可以探测到NPE。 sonar能动态捕捉JVM引起的空指针异常吗
NullPointerException
是当您试图使用一个指向内存中任何位置(null)的引用时发生的异常,就像它在引用一个对象一样。 对空引用调用方法或尝试访问空引用的字段将触发NullPointerException
。 这些是最常见的,但其他方式在NullPointerException
javadoc页面中列出。
我能想出的用来说明NullPointerException
的最快示例代码可能是:
public class Example {
public static void main(String[] args) {
Object obj = null;
obj.hashCode();
}
}
在main
内部的第一行,我显式地将对象
引用obj
设置为等于null
。 这意味着我有一个引用,但它没有指向任何对象。 之后,我尝试通过调用对象上的方法来处理引用,就像它指向一个对象一样。 这将导致NullPointerException
,因为在引用所指向的位置中没有要执行的代码。
(这是一个技术性问题,但我认为有必要提一下:指向null的引用与指向无效内存位置的C指针不同。null指针实际上不指向任何地方,这与指向碰巧无效的位置有细微的区别。)
Javadocs是一个很好的起点。 他们涵盖了以下内容:
当应用程序试图在需要对象的情况下使用null时引发。 这些措施包括:
应用程序应该抛出此类的实例,以指示null对象的其他非法使用。
还有一种情况是,如果您尝试对synchronized
使用空引用,那么也将引发此异常,根据JLS:
SynchronizedStatement:
synchronized ( Expression ) Block
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
为null,对其调用length
方法会引发异常。 我们可以看到,当从方法中移除s.length()
时,程序停止抛出异常。
跟踪这些值的来源
接下来检查这个值来自哪里。 通过跟随该方法的调用方,我们可以看到s
与printString(name)
一起在print(name)
方法中传入,并且this.name
为空。
跟踪应设置这些值的位置
this.name
设置在哪里? 在setName(String)
方法中。 通过更多的调试,我们可以看到根本没有调用这个方法。 如果调用了该方法,请确保检查调用这些方法的顺序,并且set方法不是在print方法之后调用的。
这足以给我们提供一个解决方案:在调用printer.print()
之前,添加对printer.setName()
的调用。
该变量可以具有默认值(并且setname
可以防止将其设置为null):
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();
}
}
另请参阅:
如果您试图调试该问题,但仍然没有解决方案,您可以发布一个问题以获得更多帮助,但请确保包含到目前为止已尝试的内容。 至少,在问题中包含stacktrace,并在代码中标记重要的行号。 另外,首先尝试简化代码(参见SSCCE)。