什么是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。

追踪这些值来自哪里

接下来检查此值的来源。 通过跟随方法的调用者,我们看到sprint()方法中使用printString(name)传入,而this.name为null。

跟踪应设置这些值的位置

this.name设置在哪里? 在setName(String)方法中。 通过一些更多的调试,我们可以看到根本没有调用此方法。 如果调用该方法,请确保检查调用这些方法的顺序 ,并且在print方法之后不调用set方法。

这足以为我们提供一个解决方案:在调用printer.setName()之前添加对printer.setName()调用。

其他修正

该变量可以具有默认值 (并且setName可以防止将其设置为null):

 private String name = ""; 

printprintString方法可以检查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类型分为基本类型booleanint等)和引用类型 。 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值为nullbar[pos]将抛出NPE。
  • 如果bar[pos]值为null则在其上调用length()将抛出NPE。

接下来,我们需要弄清楚哪些场景解释了实际发生的情况。 我们将从探索第一个开始:

bar从哪里来? 它是test方法调用的参数,如果我们看看如何调用test ,我们可以看到它来自foo静态变量。 另外,我们可以清楚地看到我们将foo初始化为非null值。 这足以暂时驳回这一解释。 (理论上,其他东西可以改变 foonull ……但这不会发生在这里。)

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

  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. 快速失败并使用断言来避免null对象在不应为null时在整个应用程序中传播。
  5. 首先使用equals与已知对象: if("knownObject".equals(unknownObject)
  6. 首选valueOf() over toString()。
  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. 初始化

例:

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

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