关于java:什么是NullPointerException,我该如何解决?

What is a NullPointerException, and how do I fix it?

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

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


当您声明引用变量(即对象)时,实际上是在创建指向对象的指针。考虑以下代码,在其中声明一个基本类型为int的变量:

1
2
int x;
x = 10;

在这个例子中,变量x是一个EDCOX1,0,而Java将初始化为0。当您在第二行将它赋给10时,您的值10将写入x指向的内存位置。

但是,当您试图声明引用类型时,会发生一些不同的情况。采用以下代码:

1
2
Integer num;
num = new Integer(10);

第一行声明一个名为num的变量,但它不包含基元值。相反,它包含一个指针(因为类型是Integer,它是一个引用类型)。因为你没有说什么指向Java,将它设置为NULL,意思是"我指不到任何东西"。

在第二行中,new关键字用于实例化(或创建)integer类型的对象,并为指针变量num分配该对象。现在可以使用取消引用操作符.引用对象(一个点)。

当您声明变量但没有创建对象时,您询问的Exception会发生。如果在创建对象之前试图取消引用num,则会得到NullPointerException。在最普通的情况下,编译器会发现问题并告诉您"num可能尚未初始化",但有时编写的代码不会直接创建对象。

例如,您可能有如下方法:

1
2
3
public void doSomething(SomeObject obj) {
   //do something to obj
}

在这种情况下,您不会创建对象obj,而是假设它是在调用doSomething方法之前创建的。不幸的是,可以这样调用方法:

1
doSomething(null);

在这种情况下,obj为空。如果该方法打算对传入的对象执行某些操作,则可以抛出NullPointerException,因为它是一个程序员错误,程序员需要该信息进行调试。

或者,在某些情况下,方法的目的不仅仅是对传入对象进行操作,因此可以接受空参数。在这种情况下,您需要检查空参数并以不同的方式进行操作。您还应该在文档中对此进行解释。例如,doSomething可以写成:

1
2
3
4
5
6
7
8
9
10
11
/**
  * @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
    }
}

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


NullPointerExceptions是当您试图使用指向内存中没有位置(空)的引用时发生的异常,就好像它引用了一个对象一样。对空引用调用方法或尝试访问空引用的字段将触发NullPointerException。这些是最常见的,但其他方法在NullPointerExceptionjavadoc页面中列出。

我能想到的最快的示例代码可能是:

1
2
3
4
5
6
7
8
public class Example {

    public static void main(String[] args) {
        Object obj = null;
        obj.hashCode();
    }

}

main内的第一行,我明确设置Object引用obj等于null。这意味着我有一个引用,但它没有指向任何对象。在那之后,我尝试将引用视为它通过调用一个方法指向一个对象。这会导致NullPointerException,因为在引用指向的位置没有要执行的代码。

(这是一个技术性的问题,但我认为应该提到:指向空值的引用与指向无效内存位置的C指针不同。从字面上看,空指针不指向任何地方,这与指向恰好无效的位置略有不同。)


什么是NullPointerException?

一个好的起点是javadocs。它们包括:

Thrown when an application attempts to use null in a case where an
object is required. These include:

  • Calling the instance method of a null object.
  • Accessing or modifying the field of a null object.
  • Taking the length of null as if it were an array.
  • Accessing or modifying the slots of null as if it were an array.
  • Throwing null as if it were a Throwable value.

Applications should throw instances of this class to indicate other
illegal uses of the null object.

另外,如果您尝试对synchronized使用空引用,也会根据jls引发此异常:

1
2
SynchronizedStatement:
    synchronized ( Expression ) Block
  • Otherwise, if the value of the Expression is null, a NullPointerException is thrown.

我该怎么修?

所以你有一个NullPointerException。你怎么修理它?让我们举一个简单的例子,它抛出一个NullPointerException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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();
    }
}

标识空值

第一步是准确识别导致异常的值。为此,我们需要进行一些调试。学习读stacktrace很重要。这将显示引发异常的位置:

1
2
3
4
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)方法中。通过更多的调试,我们可以看到这个方法根本没有被调用。如果调用了方法,请确保检查调用这些方法的顺序,并且不会在print方法之后调用set方法。

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

其他修正

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

1
private String name ="";

printprintString方法可以检查是否为空,例如:

1
printString((name == null) ?"" : name);

或者可以设计类,使name始终具有非空值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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中?

我还是找不到问题

如果您试图调试问题,但仍然没有解决方案,您可以发布一个问题以获得更多帮助,但请确保包含迄今为止所尝试的内容。至少在问题中包含stacktrace,并在代码中标记重要的行号。另外,尝试先简化代码(参见SSCC)。


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

如果您对上面文件中的行进行计数,那么第4行就是我用"here"注释标记的行。好的。

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

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

1-不完全正确。有一些东西叫做嵌套异常…好的。问题:如何在代码中跟踪NPE异常的原因?

这是最困难的部分。简短的答案是将逻辑推理应用于堆栈跟踪、源代码和相关API文档提供的证据。好的。

让我们先用上面的简单例子来说明。我们首先查看堆栈跟踪告诉我们NPE发生的位置的行:好的。

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

那怎么会造成不良后果呢?好的。

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

但是(我听到你说)如果NPE是在length()方法调用中抛出的呢?好的。

好吧,如果发生这种情况,堆栈跟踪看起来会有所不同。第一行"at"表示在java.lang.String类的某一行中抛出了异常,而Test.java的第4行将是第二行"at"。好的。

那么,那个null是从哪里来的呢?在这种情况下,这是显而易见的,而且很明显我们需要做什么来修复它。(为foo指定一个非空值。)好的。

好吧,让我们尝试一个稍微复杂一点的例子。这需要一些逻辑推理。好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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"线。第一个是这行:好的。

1
return args[pos].length();

第二条是这条线:好的。

1
int length = test(foo, 1);

看第一行,这怎么会造成不良事件呢?有两种方法:好的。

  • 如果bar的值是null,那么bar[pos]将抛出一个NPE。
  • 如果bar[pos]的值是null,那么对其调用length()将抛出一个NPE。

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

江户十一〔九〕是从哪里来的?它是test方法调用的参数,如果我们看看test是如何调用的,我们可以看到它来自foo静态变量。此外,我们可以清楚地看到,我们将foo初始化为非空值。这足以暂时否定这一解释。(理论上,还有别的东西可以把foo改成null……但这不是在这里发生的。)好的。

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

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

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

我们分配一个String[],其中有两个元素初始化为null。之后,我们没有改变foo的内容。因此,foo[1]仍然是null。好的。好啊。问题:什么导致了NullPointerException(NPE)?

正如你应该知道的,Java类型被划分为原始类型(EDCOX1,3,4,EDCOX1,等等)和引用类型。Java中的引用类型允许您使用特殊值EDCOX1(5),这是Java方式的"无对象"。好的。

每当你的程序试图使用一个null时,就会在运行时抛出一个NullPointerException,就好像它是一个真正的引用一样。例如,如果编写此命令:好的。

1
2
3
4
5
6
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 StackTrace?

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

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

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

第二个观察:当我运行程序时,它输出两行"GobbledyGook"。错了!!那可不是狼吞虎咽的蠢货。这是一个stacktrace…它提供了重要的信息,如果您花时间仔细阅读,它将帮助您跟踪代码中的错误。好的。

那么让我们看看它是怎么说的:好的。

1
Exception in thread"main" java.lang.NullPointerException

堆栈跟踪的第一行告诉您许多事情:好的。

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

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

1
at Test.main(Test.java:4)

这告诉我们很多事情:好的。


就像你试图访问的对象是null。请考虑以下示例:

1
TypeA objA;

此时,您刚刚声明了该对象,但没有初始化或实例化。当您试图访问其中的任何属性或方法时,它将抛出有意义的NullPointerException

请参见下面的示例:

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


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

  • 调用null对象的实例方法。
  • 访问或修改null对象的字段。
  • 把EDOCX1的长度(0)当作一个数组。
  • 访问或修改EDOCX1的插槽(0),就好像它是一个数组一样。
  • 抛出null,就好像它是一个可丢弃的值。
  • 应用程序应该抛出此类的实例,以指示其他非法使用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修饰符强制良好的初始化。
  • 避免在方法中返回空值,例如在适用时返回空集合。
  • 使用注释@NotNull@Nullable
  • 快速失败并使用断言以避免空对象在不应为空时在整个应用程序中传播。
  • 先用已知对象的等号:if("knownObject".equals(unknownObject)
  • 比起toString(),更喜欢valueOf()
  • 使用空安全StringUtils方法StringUtils.isEmpty(null)方法。

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Student {

        private int id;

        public int getId() {
            return this.id;
        }

        public setId(int newId) {
            this.id = newId;
        }
    }

    下面的代码给出了一个空指针异常。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class School {

        Student obj_Student;

        public School() {
            try {
                obj_Student.getId();
            }
            catch(Exception e) {
                System.out.println("Null Pointer");
            }
        }
    }

    因为您使用的是Obj_Student,但是忘记了像在正确的代码如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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中,一切都以类的形式出现。

    如果要使用任何对象,则有两个阶段:

  • 申报
  • 初始化
  • 例子:

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

    同样适用于阵列概念

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

    如果不提供初始化部分,则出现NullPointerException


    在Java中,所有声明的变量实际上是对对象(或基元)的引用,而不是对象本身。

    当您尝试执行一个对象方法时,引用会要求活动对象执行该方法。但如果引用引用的是空值(Nothing、Zero、Void、Nada),则无法执行该方法。然后运行时通过抛出NullPointerException让您知道这一点。

    您的引用是"指向"空值,因此是"空->指针"。

    对象位于虚拟机内存空间中,访问它的唯一方法是使用this引用。举个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Some {
        private int id;
        public int getId(){
            return this.id;
        }
        public setId( int newId ) {
            this.id = newId;
        }
    }

    在你的代码中的另一个地方:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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都指向空值时),那么对象就是"不可访问的"。我们无法使用它,所以这个对象已经准备好被垃圾收集,在某个时候,虚拟机将释放这个对象使用的内存,并分配另一个。


    当声明一个对象数组,然后立即尝试取消对其中的元素的引用时,会出现另一个NullPointerException的情况。

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

    如果比较顺序相反,则可以避免这种特殊的NPE;即,在有保证的非空对象上使用.equals

    数组中的所有元素都初始化为它们的公共初始值;对于任何类型的对象数组,这意味着所有元素都是null

    在访问或取消引用数组中的元素之前,必须对它们进行初始化。

    1
    2
    3
    4
    5
    String[] phrases = new String[] {"The bird","A bird","My bird","Bird"};
    String keyPhrase ="Bird";
    for(String phrase : phrases) {
        System.out.println(phrase.equals(keyPhrase));
    }