什么是空指针异常(java.lang.NullPointerException
)以及导致它们的原因?
可以使用哪些方法/工具来确定原因,以便停止异常导致程序过早终止?
答案 0 :(得分:3547)
当您声明一个引用变量(即一个对象)时,您实际上是在创建一个指向对象的指针。请考虑以下代码,其中声明了基本类型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 may not have been initialized
”,但有时您可能会编写不直接创建对象的代码。
例如,您可以使用以下方法:
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
}
}
答案 1 :(得分:840)
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指针不同。空指针实际上并不指向任何地方,与指向恰好无效的位置略有不同。)
答案 2 :(得分:669)
一个好的起点是JavaDocs。他们有这个涵盖:
当应用程序在某个情况下尝试使用null时抛出 对象是必需的。其中包括:
- 调用null对象的实例方法。
- 访问或修改空对象的字段。
- 将null的长度视为数组。
- 访问或修改null的插槽,就像它是一个数组一样。
- 抛出null,就好像它是一个Throwable值。
应用程序应抛出此类的实例以指示其他类 非法使用null对象。
如果您尝试对synchronized
使用空引用,也会抛出此异常,per the JLS:
SynchronizedStatement: synchronized ( Expression ) Block
- 否则,如果Expression的值为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
为null,并且在其上调用length
方法会抛出异常。我们可以看到,当从方法中删除s.length()
时,程序会停止抛出异常。
跟踪这些值的来源
接下来检查此值的来源。通过关注方法的调用者,我们看到s
方法中的printString(name)
传入print()
,this.name
为空。
跟踪应设置这些值的位置
this.name
在哪里设置?在setName(String)
方法中。通过一些更多的调试,我们可以看到这个方法根本没有被调用。如果调用了该方法,请确保检查 order 是否调用了这些方法,并且在打印方法之后,set方法不会被称为。
这足以为我们提供解决方案:在致电printer.setName()
之前添加对printer.print()
的来电。
变量可以有默认值(而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)。
答案 3 :(得分:485)
NullPointerException
(NPE)的原因是什么?正如您所知,Java类型分为原始类型(boolean
,int
等)和引用类型。 Java中的引用类型允许您使用特殊值null
,这是Java的说法"没有对象"。
当程序试图使用NullPointerException
时,就会在运行时抛出null
,就好像它是真正的引用一样。例如,如果你这样写:
public class Test {
public static void main(String[] args) {
String foo = null;
int length = foo.length(); // HERE
}
}
标有“#34; HERE"将尝试在length()
引用上运行null
方法,这将抛出NullPointerException
。
有很多方法可以使用导致null
的{{1}}值。事实上,您可以使用NullPointerException
而不会导致NPE的唯一事情是:
null
或==
运算符或!=
对其进行测试。假设我编译并运行上面的程序:
instanceof
首先观察:编译成功!程序中的问题不是编译错误。这是运行时错误。 (某些IDE可能会警告您的程序将始终抛出异常...但标准$ javac Test.java
$ java Test
Exception in thread "main" java.lang.NullPointerException
at Test.main(Test.java:4)
$
编译器不会。)
第二个观察:当我运行程序时,它会输出两行" gobbledy-gook"。 错误!! 那不是狼吞虎咽。它是一个堆栈跟踪...它提供重要信息,如果你花时间仔细阅读它,它将帮助你追踪代码中的错误。
让我们来看看它的内容:
javac
堆栈跟踪的第一行告诉你许多事情:
Exception in thread "main" java.lang.NullPointerException
。java.lang.NullPointerException
在这方面很不寻常,因为它很少有错误信息。第二行是诊断NPE中最重要的一行。
NullPointerException
这告诉了我们很多事情:
at Test.main(Test.java:4)
类的main
方法。如果你计算上面文件中的行,第4行是我用" HERE"标记的行。评价。
请注意,在一个更复杂的示例中,NPE堆栈跟踪中会有很多行。但是你可以确定第二行(第一行" at"行)将告诉你NPE被抛出的位置 1 。
简而言之,堆栈跟踪将毫不含糊地告诉我们该程序的哪个语句抛出了NPE。
1 - 不完全正确。有些东西叫做嵌套异常...
这是困难的部分。简短的回答是对堆栈跟踪,源代码和相关API文档提供的证据应用逻辑推理。
首先用简单的例子(上图)说明。我们首先看一下堆栈跟踪告诉我们NPE发生的位置:
Test
怎么能抛出NPE?
实际上只有一种方法:只有当int length = foo.length(); // HERE
具有值foo
时才会发生这种情况。然后,我们尝试在null
和.... BANG!
length()
方法
但是(我听到你说)如果NPE被抛到null
方法调用中会怎么样?
好吧,如果发生这种情况,堆栈跟踪看起来会有所不同。第一个" at" line会说在length()
类的某行中抛出了异常,而java.lang.String
的第4行将是第二个" at"线。
那Test.java
来自哪里?在这种情况下,很明显,我们需要做些什么来解决它。 (为null
指定一个非空值。)
好的,让我们尝试一个稍微棘手的例子。这将需要一些逻辑推理。
foo
所以现在我们有两个" at"线。第一个是这一行:
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)
$
,第二个就是这一行:
return args[pos].length();
看第一行,怎么会抛出一个NPE?有两种方法:
int length = test(foo, 1);
的值为bar
,则null
会抛出NPE。bar[pos]
的值为bar[pos]
,则在其上调用null
会抛出NPE。接下来,我们需要弄清楚哪些方案解释了实际发生的情况。我们将从探索第一个开始:
length()
来自哪里?它是bar
方法调用的参数,如果我们查看调用test
的方式,我们可以看到它来自test
静态变量。此外,我们可以清楚地看到我们将foo
初始化为非空值。这足以暂时驳回这一解释。 (理论上,其他东西可以更改 foo
到foo
......但这不会发生在这里。)
那么我们的第二个场景呢?好吧,我们可以看到null
是pos
,这意味着1
必须是foo[1]
。这可能吗?
null
我们为private static String[] foo = new String[2];
分配了两个初始化为String[]
的元素。之后,我们尚未更改null
的内容...因此foo
仍为foo[1]
。
答案 4 :(得分:408)
就像您正在尝试访问null
的对象一样。请考虑以下示例:
TypeA objA;
此时您只是声明此对象,但已初始化或已实例化。每当您尝试访问其中的任何属性或方法时,它都会抛出NullPointerException
,这是有道理的。
同样见下面的例子:
String a = null;
System.out.println(a.toString()); // NullPointerException will be thrown
答案 5 :(得分:347)
当应用程序在需要对象的情况下尝试使用null时,抛出空指针异常。其中包括:
null
对象的实例方法。null
对象的字段。null
的长度视为数组。null
的插槽,就好像它是一个阵列一样。null
,好像它是一个Throwable值。 应用程序应抛出此类的实例以指示null
对象的其他非法使用。
参考:http://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html
答案 6 :(得分:320)
null
指针是指向无处的指针。当您取消引用指针p
时,您说“在”p“中存储位置的数据。当p
是null
指针时,存储在p
中的位置}是nowhere
,你说的是“把这个地方的数据放在'无处''。显然,它不能这样做,所以它会抛出一个null pointer exception
。
一般情况下,这是因为某些事情尚未正确初始化。
答案 7 :(得分:308)
已经有很多解释来解释它是如何发生以及如何解决的,但您还应该遵循最佳做法来完全避免NullPointerException
。
另见: A good list of best practices
我想补充一点,非常重要的是,要好好利用final
修饰符。
Using the "final" modifier whenever applicable in Java
<强>要点:强>
final
修饰符强制执行良好的初始化。@NotNull
和@Nullable
if("knownObject".equals(unknownObject)
valueOf()
over toString()。StringUtils
方法StringUtils.isEmpty(null)
。答案 8 :(得分:302)
空指针异常是指在没有初始化的情况下使用对象的指示符。
例如,下面是一个将在我们的代码中使用它的学生班。
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");
}
}
}
答案 9 :(得分:300)
在Java中,所有内容(不包括基本类型)都是类的形式。
如果您想使用任何对象,那么您有两个阶段:
示例:
Object object;
object = new Object();
数组概念相同:
Item item[] = new Item[5];
item[0] = new Item();
如果您没有给出初始化部分,那么NullPointerException
就会出现。
答案 10 :(得分:296)
在Java中,您声明的所有变量实际上是对象(或基元)的“引用”,而不是对象本身。
当您尝试执行一个对象方法时,引用会要求生命对象执行该方法。但是如果引用引用NULL(没有,零,无效,nada),则该方法无法执行。然后运行时通过抛出NullPointerException来告诉您。
您的引用是“指向”null,因此“Null - &gt; Pointer”。
该对象位于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将释放此对象使用的内存并分配另一个。
答案 11 :(得分:273)
当一个声明一个对象数组,然后立即尝试取消引用它内部的元素时,会出现另一个NullPointerException
。
String[] phrases = new String[10];
String keyPhrase = "Bird";
for(String phrase : phrases) {
System.out.println(phrase.equals(keyPhrase));
}
如果比较顺序颠倒,可以避免这种特殊的NPE;即,在保证的非空对象上使用.equals
。
数组are initialized to their common initial value内的所有元素;对于任何类型的对象数组,这意味着所有元素都是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));
}