什么是NullPointerException,如何解决?

什么是空指针exception( java.lang.NullPointerException )以及是什么原因造成的?

可以使用哪些方法/工具来确定原因,以便阻止exception导致程序过早终止?

Solutions Collecting From Web of "什么是NullPointerException,如何解决?"

当你声明一个引用variables(即一个对象)时,你真的正在创build一个指向对象的指针。 考虑下面的代码,你声明了一个基本typesint的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 } 

在这种情况下,您不是创build对象obj ,而是假定它是在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(); } } 

main的第一行,我明确地设置Object reference obj等于null 。 这意味着我有一个参考,但它不是指向任何对象。 之后,我尝试通过调用一个方法来处理引用,就像它指向一个对象。 这将导致NullPointerException因为在引用指向的位置没有要执行的代码。

(这是一个技术性问题,但是我认为它提到了:指向null的引用与指向无效内存位置的C指针不同,空指针实际上并不指向任何地方 ,这与微妙地不同指向一个恰好无效的位置。)

什么是NullPointerException?

JavaDocs是一个很好的开始。 他们有这个覆盖:

当应用程序试图在需要对象的情况下使用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 。 为此,我们需要做一些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。

跟踪这些值来自哪里

接下来检查这个值来自哪里。 通过跟随方法的调用者,我们看到sprint()方法中用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 (NPE)的原因是什么?

正如你应该知道的那样,Javatypes被分成基本typesbooleanint等)和引用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) $ 

所以现在我们有两个“在”线。 第一个是这一行:

  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 …但这不是在这里发生的)

那么我们的第二种情况呢? 那么我们可以看到pos1 ,这意味着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。 这些包括:

  1. 调用null对象的实例方法。
  2. 访问或修改null对象的字段。
  3. null的长度视为一个数组。
  4. 像访问数组一样访问或修改null的槽。
  5. 抛出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”修饰符

概要:

  1. 使用final修饰符来强制执行良好的初始化。
  2. 避免在方法中返回null,例如在适用时返回空集合。
  3. 使用注释@NotNull@Nullable
  4. 快速失败并使用断言来避免空对象在整个应用程序中传播时,它们不应该为空。
  5. 首先使用等于一个已知对象: if("knownObject".equals(unknownObject)
  6. valueOf()于toString()的valueOf()。
  7. 使用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出现的。

如果你想使用任何对象,那么你有两个阶段:

  1. 宣布
  2. 初始化

例:

  • 声明: 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... 

这是一个重要的事情要知道 – 当没有更多的引用对象(在上面的例子中,当referenceotherReference指向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)); }