什么是NullPointerException,我该如何解决?
什么是空指针exception( java.lang.NullPointerException
)以及它们的原因是什么?
可以使用哪些方法/工具来确定原因,以便停止exception导致程序过早终止?
声明引用变量(即对象)时,实际上是在创建指向对象的指针。 请考虑以下代码,其中声明基本类型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
分配此对象。 您现在可以使用解除引用运算符引用该对象.
(一个点)。
您询问的Exception
在声明变量但未创建对象时发生。 如果尝试取消引用num
BEFORE在创建对象之前会出现NullPointerException
。 在最琐碎的情况下,编译器将捕获问题并让您知道“num可能尚未初始化”,但有时您编写的代码不会直接创建对象。
例如,您可能有如下方法:
public void doSomething(SomeObject obj) { //do something to obj }
在这种情况下,您不是在创建对象obj
,而是假设它是在调用doSomething
方法之前创建的。 不幸的是,可以像这样调用方法:
doSomething(null);
在这种情况下, obj
为null。 如果该方法旨在对传入的对象执行某些操作,则抛出NullPointerException
是合适的,因为它是程序员错误,并且程序员将需要该信息用于调试目的。
或者,可能存在这样的情况:该方法的目的不仅仅是对传入的对象进行操作,因此可以接受空参数。 在这种情况下,您需要检查null参数并采取不同的行为。 您还应该在文档中解释这一点。 例如, 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 } }
最后, 如何使用堆栈跟踪查明exception和原因
NullPointerException
是当您尝试使用指向内存中没有位置的引用(null)时发生的exception,就好像它引用了一个对象一样。 在空引用上调用方法或尝试访问空引用的字段将触发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指针不同。空指针实际上并不指向任何位置 ,这与指向恰好无效的位置。)
什么是NullPointerException?
一个好的起点是JavaDocs 。 他们有这个涵盖:
当应用程序在需要对象的情况下尝试使用null时抛出。 这些包括:
- 调用null对象的实例方法。
- 访问或修改空对象的字段。
- 将null的长度视为数组。
- 访问或修改null的插槽,就像它是一个数组一样。
- 抛出null,好像它是一个Throwable值。
应用程序应该抛出此类的实例以指示null对象的其他非法使用。
还有一种情况是,如果您尝试使用带有synchronized
的空引用,那么每个JLS也会抛出此exception:
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的值 。 为此,我们需要做一些调试。 学习阅读堆栈跟踪很重要。 这将显示抛出exception的位置:
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行抛出exception(在printString
方法中)。 查看该行并通过添加日志记录语句或使用调试器来检查哪些值为空。 我们发现s
为null,并且在其上调用length
方法会抛出exception。 我们可以看到,当从方法中删除s.length()
时,程序停止抛出exception。
追踪这些值来自哪里
接下来检查此值的来源。 通过跟随方法的调用者,我们看到s
在print()
方法中使用printString(name)
传入,而this.name
为null。
跟踪应设置这些值的位置
this.name
设置在哪里? 在setName(String)
方法中。 通过一些更多的调试,我们可以看到根本没有调用此方法。 如果调用该方法,请确保检查调用这些方法的顺序 ,并且在print方法之后不调用set方法。
这足以为我们提供一个解决方案:在调用printer.setName()
之前添加对printer.setName()
调用。
其他修正
该变量可以具有默认值 (并且setName
可以防止将其设置为null):
private String name = "";
print
或printString
方法可以检查null ,例如:
printString((name == null) ? "" : name);
或者,您可以设计类,以便name
始终具有非null值 :
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(); } }
也可以看看:
- 在Java中避免使用“!= null”语句?
我仍然找不到问题
如果您尝试调试问题但仍然没有解决方案,可以发布问题以获得更多帮助,但请确保包含您目前为止尝试过的内容。 至少要在问题中包含堆栈跟踪 ,并在代码中标记重要的行号 。 另外,请先尝试简化代码(请参阅SSCCE )。
问题:导致NullPointerException
(NPE)的原因是什么?
您应该知道,Java类型分为基本类型 ( boolean
, int
等)和引用类型 。 Java中的引用类型允许您使用特殊值null
,这是Java表示“无对象”的方式。
每当程序尝试使用null
时,就会在运行时抛出NullPointerException
,就像它是真正的引用一样。 例如,如果你这样写:
public class Test { public static void main(String[] args) { String foo = null; int length = foo.length(); // HERE } }
标记为“HERE”的语句将尝试在null
引用上运行length()
方法,这将抛出NullPointerException
。
有许多方法可以使用null
值,这将导致NullPointerException
。 实际上,在没有导致NPE的情况下,使用null
可以执行的唯一操作是:
- 将其分配给引用变量或从引用变量中读取它,
- 将它分配给数组元素或从数组元素中读取它(假设数组引用本身是非空的!),
- 将其作为参数传递或作为结果返回,或
- 使用
==
或!=
运算符或instanceof
测试它。
问题:如何阅读NPE堆栈跟踪?
假设我编译并运行上面的程序:
$ javac Test.java $ java Test Exception in thread "main" java.lang.NullPointerException at Test.main(Test.java:4) $
首先观察:编译成功! 程序中的问题不是编译错误。 这是一个运行时错误。 (某些IDE可能会警告您的程序将始终抛出exception……但标准的javac
编译器不会。)
第二个观察:当我运行程序时,它会输出两行“gobbledy-gook”。 错误!! 那不是狼吞虎咽。 它是一个堆栈跟踪…它提供了重要的信息 ,可以帮助您跟踪代码中的错误,如果您花时间仔细阅读它。
那么让我们来看看它的内容:
Exception in thread "main" java.lang.NullPointerException
堆栈跟踪的第一行告诉您许多事情:
- 它告诉您抛出exception的Java线程的名称。 对于一个带有一个线程的简单程序(比如这个),它将是“主”。 让我们继续 …
- 它告诉你抛出的exception的全名; 即
java.lang.NullPointerException
。 - 如果exception具有关联的错误消息,则将在exception名称后输出。
NullPointerException
在这方面很不寻常,因为它很少有错误消息。
第二行是诊断NPE中最重要的一行。
at Test.main(Test.java:4)
这告诉了我们许多事情:
- “在Test.main”说我们是
Test
类的main
方法。 - “Test.java:4”给出了类的源文件名,它告诉我们发生这种情况的语句在文件的第4行。
如果你计算上面文件中的行,第4行是我用“HERE”注释标记的行。
请注意,在一个更复杂的示例中,NPE堆栈跟踪中会有很多行。 但是你可以确定第二行(第一行“at”行)将告诉你NPE被抛出的位置1 。
简而言之,堆栈跟踪将毫不含糊地告诉我们程序的哪个语句抛出了NPE。
1 – 不完全正确。 有些东西叫做嵌套exception……
问题:如何在代码中跟踪NPEexception的原因?
这是困难的部分。 简短的回答是对堆栈跟踪,源代码和相关API文档提供的证据应用逻辑推理。
让我们首先用简单的例子(上面)来说明。 我们首先看一下堆栈跟踪告诉我们NPE发生的位置:
int length = foo.length(); // HERE
如何抛出NPE?
实际上只有一种方法:只有当foo
的值为null
才会发生。 然后我们尝试在null
和….上运行length()
方法!
但是(我听你说)如果NPE被引入length()
方法调用中会怎么样?
好吧,如果发生这种情况,堆栈跟踪看起来会有所不同。 第一个“at”行表示exception是在java.lang.String
类的某行中抛出的, Test.java
的第4行是第二个“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
初始化为非null值。 这足以暂时驳回这一解释。 (理论上,其他东西可以改变 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时,将引发空指针exception。 这些包括:
- 调用
null
对象的实例方法。 - 访问或修改
null
对象的字段。 - 将
null
的长度视为数组。 - 访问或修改
null
的插槽,就像它是一个数组一样。 - 抛出
null
,好像它是一个Throwable值。
应用程序应该抛出此类的实例以指示null
对象的其他非法使用。
参考: http : //docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html
NULL
指针是指向无处的指针。 当您取消引用指针p
,您说“给我存储在”p“中的位置的数据。当p
是空指针时,存储在p
的位置nowhere
,您说”在该位置给我数据’无处’。显然,它无法执行此操作,因此会抛出NULL pointer exception
。
一般来说,这是因为某些东西尚未正确初始化。
已经有很多解释来解释它是如何发生的以及如何解决它,但你也应该遵循最佳实践来避免NullPointerException
。
另请参阅: 最佳实践清单
我想补充一点,非常重要的是,要充分利用final
修饰符。 在Java中适用时使用“final”修饰符
摘要:
- 使用
final
修饰符强制执行良好的初始化。 - 避免在方法中返回null,例如在适用时返回空集合。
- 使用注释
@NotNull
和@Nullable
- 快速失败并使用断言来避免null对象在不应为null时在整个应用程序中传播。
- 首先使用equals与已知对象:
if("knownObject".equals(unknownObject)
- 首选
valueOf()
over toString()。 - 使用null安全
StringUtils
方法StringUtils.isEmpty(null)
。
空指针exception是指示您正在使用对象而不初始化它。
例如,下面是一个将在我们的代码中使用它的学生类。
public class Student { private int id; public int getId() { return this.id; } public setId(int newId) { this.id = newId; } }
下面的代码为您提供了空指针exception。
public class School { Student obj_Student; public School() { try { obj_Student.getId(); } catch(Exception e) { System.out.println("Null Pointer "); } } }
因为您使用的是Obj_Student
,但是您忘记将其初始化,就像在下面显示的正确代码中一样:
public class School { Student obj_Student; public School() { try { obj_Student = new Student(); obj_Student.setId(12); obj_Student.getId(); } catch(Exception e) { System.out.println("Null Pointer "); } } }
在Java中,一切都是以类的forms出现的。
如果您想使用任何对象,那么您有两个阶段:
- 宣布
- 初始化
例:
- 声明:
Object a;
- 初始化:
a=new Object();
对于arrays概念也是如此
- 声明:
Item i[]=new Item[5];
- 初始化:
i[0]=new Item();
如果您没有给出初始化部分, NullpointerException
出现NullpointerException
。
在Java中 ,您声明的所有变量实际上都是对象(或基元)的“引用”,而不是对象本身。
当您尝试执行一个对象方法时,该引用会要求生命对象执行该方法。 但是如果引用引用NULL(nothing,zero,void,nada),则该方法无法执行。 然后运行时通过抛出NullPointerException让您知道这一点。
你的引用是“指向”null,因此“Null – > 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将释放此对象使用的内存并将分配另一个。
当一个声明一个对象数组时,会发生另一次NullPointerException
,然后立即尝试取消引用它内部的元素。
String[] phrases = new String[10]; String keyPhrase = "Bird"; for(String phrase : phrases) { System.out.println(phrase.equals(keyPhrase)); }
如果比较顺序颠倒,则可以避免这种特殊的NPE; 即,在保证的非null对象上使用.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)); }