什么是NullPointerException,如何解决?
什么是空指针exception( java.lang.NullPointerException
)以及是什么原因造成的?
可以使用哪些方法/工具来确定原因,以便阻止exception导致程序过早终止?
当你声明一个引用variables(即一个对象)时,你真的正在创build一个指向对象的指针。 考虑下面的代码,你声明了一个原始types为int
的variables:
int x; x = 10;
在这个例子中,variablesx是一个int
,Java会将它初始化为0。 在第二行中将其赋值为10时,值10将被写入x所指向的内存位置。
但是,当你试图声明一个引用types时会发生不同的事情。 采取以下代码:
Integer num; num = new Integer(10);
第一行声明了一个名为num
的variables,但是它不包含原始值。 相反,它包含一个指针(因为types是Integer
,它是一个引用types)。 既然你还没有说到什么地方,Java把它设置为空,意思是“我没有指向什么”。
在第二行中, new
关键字用于实例化(或创build)一个Integertypes的对象,并且指针variablesnum被分配给这个对象。 现在可以使用解引用操作符引用对象.
(一个点)。
当你声明一个variables但是没有创build一个对象时,你询问的Exception
就会发生。 如果您在创build对象之前尝试解除引用num
,则会得到NullPointerException
。 在最微不足道的情况下,编译器会捕获这个问题,并让你知道“num可能没有被初始化”,但是有时你编写的代码不能直接创build对象。
例如,你可能有一个方法如下:
public void doSomething(SomeObject obj){ //do something to obj }
在这种情况下,你不是创buildobj
,而是假定是在doSomething
方法被调用之前创build的。 不幸的是,可以这样调用方法:
doSomething(null);
在这种情况下obj
是空的。 如果该方法打算对传入的对象执行某些操作,则抛出NullPointerException
是合适的,因为这是程序员错误,程序员将需要这些信息用于debugging目的。
或者,可能存在这样的情况:方法的目的不仅仅是对传入的对象进行操作,并且因此可能接受空参数。 在这种情况下,您需要检查一个空参数,并且行为不同。 你也应该在文档中解释这一点。 例如, 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
是当您尝试使用指向内存中没有位置的引用(空)时发生的exception,就像引用对象一样。 在空引用上调用方法或试图访问空引用的字段将触发NullPointerException
。 这些是最常见的,但NullPointerException
javadoc页面上列出了其他方法。
也许我能想到的最快的示例代码来说明一个NullPointerException
:
public class Example { public static void main(String[] args) { Object obj = null; obj.hashCode(); } }
在主内部的第一行我明确地设置Object
引用obj
等于null。 这意味着我有一个参考,但它不是指向任何对象。 之后,我尝试通过调用一个方法来对待引用,就像它指向一个对象。 这将导致NullPointerException
因为在引用指向的位置没有要执行的代码。
(这是一个技术性问题,但是我认为它提到了:指向null的引用与指向无效内存位置的C指针不同,空指针实际上并不指向任何地方 ,这与微妙地不同指向一个恰好无效的位置。)
什么是NullPointerException?
JavaDocs是一个很好的开始。 他们有这个覆盖:
当应用程序试图在需要对象的情况下使用null时抛出。 这些包括:
- 调用空对象的实例方法。
- 访问或修改空对象的字段。
- 将null的长度视为一个数组。
- 像访问数组一样访问或修改null的插槽。
- 抛出null,就像它是一个Throwable值一样。
应用程序应抛出此类的实例来指示其他非法使用null对象。
同样的情况是,如果你尝试使用带有synchronized
的空引用,那么也会抛出这个exception, 每个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 。 为此,我们需要做一些debugging。 学习阅读堆栈跟踪很重要。 这将告诉你在什么地方抛出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方法中)。 查看行并通过添加日志语句或使用debugging器来检查哪些值为空。 我们发现s
是空的,调用它的length
方法会抛出exception。 当s.length()
从方法中移除时,我们可以看到程序停止抛出exception。
跟踪这些值来自哪里
接下来检查这个值来自哪里。 通过跟随方法的调用者,我们看到s
在print()
方法中用printString(name)
传入,并且this.name
为null。
跟踪这些值应该被设置
在哪里设置? 在setName(String)
方法中。 通过一些更多的debugging,我们可以看到这个方法根本不被调用。 如果调用该方法,请确保检查这些方法的调用顺序 ,并且在打印方法之后不调用set方法。
这足以给我们一个解决scheme:在调用printer.setName()
之前,调用printer.print()
。
其他修复
该variables可以有一个默认值 (和setName
可以防止它被设置为空):
private String name = "";
print
或者printString
方法可以检查null ,例如:
printString((name == null) ? "" : name);
或者你可以devise这个类,使得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(); } }
也可以看看:
- 在Java中避免“!= null”语句?
我仍然无法find问题
如果您尝试debugging问题,但仍然没有解决scheme,则可以发布一个问题以获得更多帮助,但请确保包含迄今已尝试的内容。 至less在问题中包含堆栈跟踪 ,并在代码中标记重要的行号 。 另外,请先尝试简化代码(请参阅SSCCE )。
问题:导致NullPointerException
原因是什么?
正如你应该知道的那样,Javatypes被分成基本types ( boolean
, int
等)和引用types 。 Java中的引用types允许您使用特殊值null
,这是Java的“无对象”方式。
运行时抛出NullPointerException
,只要程序尝试使用null
就好像它是一个真实的引用。 例如,如果你写这个:
public class Test { public static void main(String[] args) { String foo = null; int length = foo.length(); // HERE } }
标记为“HERE”的语句将尝试在null
引用上运行length()
方法,这将引发NullPointerException
。
有很多方法可以使用null
值,这将导致NullPointerException
。 如果事实上,你可以做一个null
而不会导致一个NPE的唯一的事情是:
- 将其分配给参考variables或从参考variables中读取,
- 把它分配给一个数组元素或者从一个数组元素中读取(假设数组引用本身是非空的!),
- 将其作为parameter passing或作为结果返回,或者
- 使用
==
或!=
运算符或instanceof
对其进行testing。
问题:我如何阅读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
在这方面是不常见的,因为它很less有错误消息。
第二行是诊断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()
方法的length()
方法。BANG!
但是(我听到你说)如果NPE在length()
方法调用中被抛出怎么办?
那么如果发生这种情况,堆栈跟踪看起来会不一样。 第一个“at”行会说在java.lang.String
类的某一行抛出exception,而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) $
所以现在我们有2个“在线”。 第一个是这一行:
return args[pos].length();
第二个是这条线:
int length = test(foo, 1);
所以看第一行,怎么会抛出一个NPE? 其实有两种方法:
- 如果
bar
值为null
那么bar[pos]
会抛出一个NPE。 - 如果
bar[pos]
值为null
那么调用length()
将抛出一个NPE。
所以接下来我们需要弄清哪些场景解释了实际发生的事情。 让我们开始探索第一个:
bar
从哪里来? 它是test
方法调用的一个参数,如果我们看看如何调用test
,我们可以看到它来自foo
静态variables。 我们可以清楚地看到,我们将foo
初始化为一个非空值。 暂时解雇这个解释就足够了。 (理论上,别的东西可能foo
变成null
…但这不是在这里发生的)
那么我们的第二种情况呢? 那么我们可以看到pos
是1
,所以这意味着foo[1]
必须是null
。 那可能吗?
它的确是! 这就是问题所在。 当我们像这样初始化时:
private static String[] foo = new String[2];
我们用两个初始化为null
元素分配一个String[]
。 然后我们没有改变foo
的内容…所以foo[1]
仍然是null
。
当您取消引用指向null
的variables时,会导致空指针exception。 看下面的代码:
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
- 快速失败并使用断言来避免空对象在不应为空的情况下通过整个应用程序的传播。
- 首先使用与已知对象的equals:
if("knownObject".equals(unknownObject)
-
valueOf()
于toString()的valueOf()。 - 使用null安全的
StringUtils
方法StringUtils.isEmpty(null)
。
空指针exception是指您正在使用Object而不初始化它。
例如下面是一个将在我们的代码中使用的学生类。
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出现的。
如果你想使用任何对象,那么你有两个阶段
- 宣布
- 初始化
例:
- 声明:
int a;
- 初始化:
a=0;
数组概念相同
- 声明:
Item i[]=new Item[5];
- 初始化:
i[0]=new Item();
如果没有给出初始化部分, NullpointerException
出现NullpointerException
。
在Java中,所有声明的variables实际上是对对象(或基元)的“引用”,而不是对象本身。
当您尝试执行一个对象方法时,引用请求活动对象执行该方法。 但是如果引用是引用NULL(nothing,zero,void,nada),那么方法就没有办法执行了。 然后运行时让你知道这个抛出一个NullPointerException。
您的参考是“指向”空,因此“空 – >指针”。
该对象位于VM内存空间中,访问它的唯一方法是使用this
引用。 以这个例子:
public class Some { private int id; public int getId(){ return this.id; } public setId( int newId ) { this.id = newId; } } .... .... // Somewhere else... 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...
这是一个重要的事情要知道 – 当没有更多的引用对象(在上面的例子中,当“引用”和“otherReference”指向null),那么对象是“无法访问”。 我们无法处理它,所以这个对象被标记为被垃圾收集,并且在某个时刻,VM将释放这个对象使用的内存,并分配另一个内存。
当一个声明一个对象数组时,会发生另一个NullPointerException
,然后立即尝试解除引用其中的元素。
String[] phrases = new String[10]; String keyPhrase = "Bird"; for(String phrase : phrases) { System.out.println(phrase.equals(keyPhrase)); }
如果比较顺序颠倒,这个特定的NPE是可以避免的; 即在保证的非空对象上使用.equals
。
数组内的所有元素都被初始化为它们的公共初始值 ; 对于任何types的对象数组,这意味着所有的元素都是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)); }