什么是NullPointerException,我如何修复它?

什么是空指针异常(java.lang.NullPointerException),是什么原因导致的?

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

3838693 次浏览

__abc0是当你试图使用指向内存中任何位置(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指针不同。空指针字面上不指向在任何地方,这与指向一个恰好无效的位置有微妙的不同。)

这就像你试图访问一个null对象。考虑下面的例子:

TypeA objA;

此时你只有宣布这个对象,而没有初始化或实例化。当你试图访问其中的任何属性或方法时,它将抛出NullPointerException,这是有意义的。

请看下面的例子:

String a = null;
System.out.println(a.toString()); // NullPointerException will be thrown

null指针是指向任何地方的指针。当你解引用指针p时,你说“给我存储在“p”中的位置的数据。当p是一个null指针时,存储在p中的位置是nowhere,你是在说“给我位置‘无处’的数据”。显然,它不能这样做,所以它抛出null pointer exception

一般来说,这是因为某些东西没有正确地初始化。

在Java中有两种主要类型的变量:

  1. 原语:包含数据的变量。如果你想在一个基本变量中操作数据,你可以直接操作这个变量。按照惯例,基元类型以小写字母开头。例如,类型为intchar的变量就是基元。

  2. 参考文献:包含Object的内存地址的变量,即请参考Object的变量。如果你想操纵引用变量引用的Object,你必须废弃它。解引用通常需要使用.来访问方法或字段,或使用[来索引数组。按照惯例,引用类型通常用大写开头的类型表示。例如,类型为Object的变量就是引用。

考虑下面的代码,其中声明了一个原始的类型的变量int,并且不初始化它:

int x;
int y = x + x;

这两行代码将使程序崩溃,因为没有为x指定值,而我们正试图使用x的值来指定y。所有原语在被操作之前都必须初始化为可用的值。

现在事情变得有趣了。参考变量可以设置为null,意思是“__abc4”。如果你以这种方式显式地设置引用变量,或者引用变量未初始化且编译器没有捕获它,则可以在引用变量中获得null值(Java将自动将变量设置为null)。

如果一个引用变量被你显式地或通过Java自动地设置为null,并且你试图废弃它,你会得到一个NullPointerException

NullPointerException (NPE)通常发生在你声明了一个变量,但在尝试使用变量的内容之前没有创建一个对象并将其赋值给变量。所以你得到了一个并不存在的东西。

取以下代码:

Integer num;
num = new Integer(10);

第一行声明了一个名为num的变量,但它实际上还没有包含一个引用值。因为你还没有说指向什么,所以Java将它设置为null

在第二行中,new关键字用于实例化(或创建)Integer类型的对象,并将引用变量num分配给该Integer对象。

如果你试图解引用num 之前创建对象,你会得到一个NullPointerException。在最简单的情况下,编译器会发现问题,并让你知道"num may not have been initialized,"但有时您可能会编写不直接创建对象的代码。

例如,你可能有这样一个方法:

public void doSomething(SomeObject obj) {
// Do something to obj, assumes obj is not null
obj.myMethod();
}

在这种情况下,你不是在创建对象obj,而是假设它是在调用doSomething()方法之前创建的。注意,可以像这样调用该方法:

doSomething(null);

在这种情况下,objnull,语句obj.myMethod()将抛出NullPointerException

如果该方法打算像上面的方法那样对传入的对象做一些事情,抛出NullPointerException是合适的,因为这是一个程序员错误,程序员将需要该信息用于调试目的。

除了由于方法逻辑而抛出的__abc0之外,你还可以检查方法参数的null值,并通过在方法开头附近添加如下内容显式抛出npe:

// Throws an NPE with a custom error message if obj is null
Objects.requireNonNull(obj, "obj must not be null");

请注意,在错误消息中明确表示哪一个对象不能为null是很有帮助的。验证这一点的好处是:1)您可以返回自己更清晰的错误消息;2)对于方法的其余部分,您知道除非obj被重新赋值,否则它不是空的,可以安全地解除引用。

或者,在某些情况下,方法的目的不仅仅是对传入的对象进行操作,因此空参数可能是可以接受的。在这种情况下,您将需要检查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
}
}

最后,如何确定异常&使用堆栈跟踪的原因

可以使用什么方法/工具来确定原因,以便您停止 导致程序过早终止的异常?< / p >

声纳与发现漏洞可以检测到NPE。 声纳能捕捉JVM动态引起的空指针异常吗 < / p >

现在Java 14添加了一个新的语言特性来显示NullPointerException的根本原因。该语言特性自2006年以来一直是SAP商业JVM的一部分。

在Java 14中,下面是一个示例NullPointerException异常消息:

在主线“main”中;java.lang.NullPointerException:不能调用“;java.util.List.size()”;因为“list"为空

导致NullPointerException发生的情况列表

以下是Java语言规范中直接*提到的所有NullPointerException发生的情况:

  • 访问(即获取或设置)空引用的实例字段。(静态字段不算数!)
  • 调用空引用的实例方法。(静态方法不算数!)
  • throw null;
  • 访问空数组的元素。
  • 同步null - synchronized (someNullReference) { ... }
  • 任何整数/浮点运算符都可以抛出NullPointerException,如果它的一个操作数是盒装的空引用
  • 如果被装箱的值为空,则开箱转换将抛出NullPointerException
  • 在空引用上调用super会抛出NullPointerException。如果你感到困惑,这是在谈论限定的超类构造函数调用:
class Outer {
class Inner {}
}
class ChildOfInner extends Outer.Inner {
ChildOfInner(Outer o) {
o.super(); // if o is null, NPE gets thrown
}
}
  • 使用for (element : iterable)循环遍历空集合/数组。

  • switch (foo) { ... }(无论是表达式还是语句)可以在foo为空时抛出NullPointerException

  • foo.new SomeInnerClass()foo为空时抛出NullPointerException

  • name1primaryExpression计算为null时,形式为name1::name2primaryExpression::name的方法引用抛出NullPointerException

    这里JLS的一个注释说,someInstance.someStaticMethod()不会抛出NPE,因为someStaticMethod是静态的,但someInstance::someStaticMethod仍然抛出NPE!

*注意,JLS可能也间接地说了很多关于NPEs

Java中,你声明的所有变量实际上都是对象(或原语)的“引用”,而不是对象本身。

当您尝试执行一个对象方法时,引用会要求活动对象执行该方法。但是如果引用引用的是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;
}
}

在代码的另一个地方:

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将释放该对象使用的内存,并分配另一个内存。

在Java中,所有东西(不包括基本类型)都是类的形式。

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

  1. 声明
  2. 初始化

例子:

  • 声明:Object object;
  • 初始化:object = new Object();

数组的概念也一样:

  • 声明:Item item[] = new Item[5];
  • 初始化:item[0] = new Item();

如果没有给出初始化部分,则会出现NullPointerException

当应用程序试图在需要对象的情况下使用null时,将引发空指针异常。这些包括:

  1. 调用null对象的实例方法。
  2. 访问或修改null对象的字段。
  3. 获取null的长度,就像它是一个数组一样。
  4. 访问或修改null的槽位,就像它是一个数组一样。
  5. 抛出null,好像它是一个Throwable值。

应用程序应该抛出该类的实例,以指示null对象的其他非法使用。

参考:http://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html

空指针异常表示您正在使用一个对象而没有初始化它。

例如,下面是一个学生类,将在我们的代码中使用它。

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");
}
}
}

NullPointerException的另一种情况发生在声明对象数组时,然后立即尝试对其中的元素进行解引用。

String[] phrases = new String[10];
String keyPhrase = "Bird";
for(String phrase : phrases) {
System.out.println(phrase.equals(keyPhrase));
}

如果比较顺序颠倒,则可以避免这种特定的NPE;也就是说,在一个有保证的非空对象上使用.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));
}

什么是NullPointerException?

JavaDocs是一个很好的开始。他们涵盖了以下内容:

当应用程序试图使用null时抛出 对象是必需的。这些包括:< / p >

  • 调用空对象的实例方法。
  • 访问或修改空对象的字段。
  • 取null的长度,就好像它是一个数组一样。
  • 访问或修改null的槽位,就像它是一个数组一样。
  • 抛出null,就好像它是一个Throwable值。
应用程序应该抛出这个类的实例来指示其他

还有一种情况是,如果你试图对synchronized使用空引用,也会抛出这个异常,根据JLS:

SynchronizedStatement:
synchronized ( Expression ) Block
  • 否则,如果表达式的值为空,则抛出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方法中)。查看这一行并检查哪些值为空 添加日志语句或使用调试器。我们发现s为空,对其调用length方法会引发异常。我们可以看到,当s.length()从方法中移除时,程序停止抛出异常

跟踪这些值的来源

接下来检查这个值的来源。通过跟踪该方法的调用者,我们可以看到sprint()方法中与printString(name)一起传入,而this.name为空。

跟踪应该在哪里设置这些值

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

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

其他修复

变量可以有默认值(并且setName可以防止它被设置为null):

private String name = "";

printprintString方法都可以检查空值,例如:

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)。

问:是什么导致了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。事实上,你可以null所做的唯一不引起NPE的事情是:

  • 将其赋值给引用变量或从引用变量中读取,
  • 将其赋值给数组元素或从数组元素中读取(前提是数组引用本身是非空的!)
  • 将其作为参数传递或作为结果返回,或者
  • 使用==!=操作符或instanceof测试它。

问:如何读取NPE堆栈跟踪?

假设我编译并运行上面的程序:

$ javac Test.java
$ java Test
Exception in thread "main" java.lang.NullPointerException
at Test.main(Test.java:4)
$

第一个观察:编译成功!程序中的问题不是编译错误。这是一个运行时错误。(一些ide可能会警告你的程序总是抛出异常…但是标准的javac编译器没有。)

第二个观察结果:当我运行这个程序时,它输出了两行“gobbledy-gook”。那不是官话。这是一个堆栈跟踪…并且它提供了重要信息,如果你花时间仔细阅读它,它将帮助你追踪代码中的错误。

让我们看看它说了什么:

Exception in thread "main" java.lang.NullPointerException

堆栈跟踪的第一行告诉你一些事情:

  • 它告诉您抛出异常的Java线程的名称。对于一个只有一个线程的简单程序(就像这个),它将是“main”。让我们继续…
  • 它告诉你所抛出的异常的全名;即java.lang.NullPointerException
  • 如果异常具有关联的错误消息,则该错误消息将在异常名称之后输出。NullPointerException在这方面是不寻常的,因为它很少有错误消息。

第二行是诊断NPE最重要的一行。

at Test.main(Test.java:4)

这告诉我们一些事情:

  • “在Test.main"表示我们在Test类的main方法中。
  • “Test.java: 4“给出了类的源文件名,并且它告诉我们发生这种情况的语句在文件的第4行。

如果你数一下上面文件中的行数,第4行就是我用&;here &;发表评论。

注意,在一个更复杂的示例中,NPE堆栈跟踪中将有很多行。但你可以肯定的是,第二行(第一个"at"line)会告诉你NPE是在哪里抛出的。

简而言之,堆栈跟踪将明确地告诉我们程序的哪条语句抛出了NPE。

参见:什么是堆栈跟踪,如何使用它来调试应用程序错误?

1 -不完全正确。有一种东西叫做嵌套异常……

问:如何在代码中追踪NPE异常的原因?

这是最难的部分。简单的回答是对堆栈跟踪、源代码和相关API文档提供的证据应用逻辑推理。

让我们先用上面的简单例子来说明。我们从堆栈跟踪告诉我们NPE发生的那行开始:

int length = foo.length(); // HERE

这怎么能引发NPE呢?

事实上,只有一种方法:只有当foo的值为null时才会发生。然后我们尝试在null上运行length()方法,然后…砰!

但是(我听到你说)如果NPE被扔在length()方法调用里面呢?

如果发生了这种情况,堆栈跟踪看起来就不一样了。第一个"java.lang.String类的第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初始化为一个非空值。这足以暂时否定这种解释。(理论上,其他东西可以改变 foonull…但这并没有发生在这里。)

那么第二种情况呢?好吧,我们可以看到pos1,所以这意味着foo[1]必须是null。这可能吗?

的确如此!这就是问题所在。当我们这样初始化时:

private static String[] foo = new String[2];

我们分配了一个包含两个元素初始化为nullString[]。在那之后,我们没有改变foo的内容…所以foo[1]仍然是null

Android系统呢?

在Android上,追踪NPE的直接原因要简单一些。异常消息通常会告诉你空引用的(编译时)类型,你正在使用而且方法,当NPE被抛出时,你试图调用。这简化了查明直接原因的过程。

但另一方面,Android有一些常见的平台特定原因导致npe。一个非常常见的情况是getViewById意外地返回null。我的建议是搜索关于意外null返回值的原因。

已经有很多解释来解释它是如何发生的以及如何修复它,但你也应该遵循最佳实践来避免__abc2。

< p >参见: 一个很好的最佳实践列表 < / p > 我要补充一点,非常重要的是,充分利用final修饰符。 使用“final"修饰符,只要在Java中适用 < / p >

简介:

  1. 使用final修饰符强制良好的初始化。
  2. 避免在方法中返回null,例如在适用时返回空集合。
  3. 使用注释@NotNull@Nullable
  4. 快速失败并使用断言来避免在null对象不应该为空时在整个应用程序中传播。
  5. 首先对已知对象使用等号:if("knownObject".equals(unknownObject)
  6. 首选valueOf()而不是toString()
  7. 使用空安全的StringUtils方法StringUtils.isEmpty(null)
  8. 在方法中使用Java 8 Optional作为返回值,Optional类提供了一种表示可选值而不是空引用的解决方案。