什么是NullPointerException,我该如何解决?

时间:2008-10-20 13:18:10

标签: java nullpointerexception

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

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

12 个答案:

答案 0 :(得分:3547)

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

int x;
x = 10;

在此示例中,变量xint,Java会将其初始化为0。在第二行为其分配值10时,10的值将写入x引用的内存位置。

但是,当您尝试声明引用类型时,会发生不同的事情。请使用以下代码:

Integer num;
num = new Integer(10);

第一行声明了一个名为num的变量,但它实际上并不包含原始值。相反,它包含一个指针(因为类型是Integer,它是一个引用类型)。由于您尚未说明要指出的内容,因此Java会将其设置为null,这意味着“我指向 nothing ”。

在第二行中,new关键字用于实例化(或创建)Integer类型的对象,指针变量num被分配给Integer宾语。

当您声明变量但未创建对象时,会发生NullPointerException。所以你指的是实际上并不存在的东西。

如果您尝试取消引用num在创建对象之前,您将获得NullPointerException。在最琐碎的情况下,编译器将捕获问题并让您知道“num may not have been initialized”,但有时您可能会编写不直接创建对象的代码。

例如,您可以使用以下方法:

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

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

doSomething(null);

在这种情况下,objnull。如果该方法旨在对传入的对象执行某些操作,则抛出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
    }
}

最后,How to pinpoint the exception & cause using Stack Trace

答案 1 :(得分:840)

NullPointerException是当您尝试使用指向内存中没有位置的引用(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指针不同。空指针实际上并不指向任何地方,与指向恰好无效的位置略有不同。)

答案 2 :(得分:669)

什么是NullPointerException?

一个好的起点是JavaDocs。他们有这个涵盖:

  

当应用程序在某个情况下尝试使用null时抛出   对象是必需的。其中包括:

     
      
  • 调用null对象的实例方法。
  •   
  • 访问或修改空对象的字段。
  •   
  • 将null的长度视为数组。
  •   
  • 访问或修改null的插槽,就像它是一个数组一样。
  •   
  • 抛出null,就好像它是一个Throwable值。
  •   
     

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

如果您尝试对synchronized使用空引用,也会抛出此异常,per the 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 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方法中)。查看该行并检查哪些值为null 添加日志记录语句或使用调试程序。我们发现s为null,并且在其上调用length方法会抛出异常。我们可以看到,当从方法中删除s.length()时,程序会停止抛出异常。

跟踪这些值的来源

接下来检查此值的来源。通过关注方法的调用者,我们看到s方法中的printString(name)传入print()this.name为空。

跟踪应设置这些值的位置

this.name在哪里设置?在setName(String)方法中。通过一些更多的调试,我们可以看到这个方法根本没有被调用。如果调用了该方法,请确保检查 order 是否调用了这些方法,并且在打印方法之后,set方法不会被称为

这足以为我们提供解决方案:在致电printer.setName()之前添加对printer.print()的来电。

其他修正

变量可以有默认值(而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)。

答案 3 :(得分:485)

问题:导致NullPointerException(NPE)的原因是什么?

正如您所知,Java类型分为原始类型booleanint等)和引用类型。 Java中的引用类型允许您使用特殊值null,这是Java的说法"没有对象"。

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

public class Test {
    public static void main(String[] args) {
        String foo = null;
        int length = foo.length();   // HERE
    }
}

标有“#34; HERE"将尝试在length()引用上运行null方法,这将抛出NullPointerException

有很多方法可以使用导致null的{​​{1}}值。事实上,您可以使用NullPointerException而不会导致NPE的唯一事情是:

  • 将其分配给引用变量或从引用变量中读取
  • 将其分配给数组元素或从数组元素中读取(假设数组引用本身为非null!),
  • 将其作为参数传递或作为结果返回,或
  • 使用null==运算符或!=对其进行测试。

问题:如何阅读NPE堆栈跟踪?

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

instanceof

首先观察:编译成功!程序中的问题不是编译错误。这是运行时错误。 (某些IDE可能会警告您的程序将始终抛出异常...但标准$ javac Test.java $ java Test Exception in thread "main" java.lang.NullPointerException at Test.main(Test.java:4) $ 编译器不会。)

第二个观察:当我运行程序时,它会输出两行" gobbledy-gook"。 错误!! 那不是狼吞虎咽。它是一个堆栈跟踪...它提供重要信息,如果你花时间仔细阅读它,它将帮助你追踪代码中的错误。

让我们来看看它的内容:

javac

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

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

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

NullPointerException

这告诉了我们很多事情:

  • "在Test.main"说我们使用at Test.main(Test.java:4) 类的main方法。
  • " Test.java:4"给出了类的源文件名,它告诉我们发生这种情况的语句在文件的第4行。

如果你计算上面文件中的行,第4行是我用" HERE"标记的行。评价。

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

简而言之,堆栈跟踪将毫不含糊地告诉我们该程序的哪个语句抛出了NPE。

1 - 不完全正确。有些东西叫做嵌套异常...

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

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

首先用简单的例子(上图)说明。我们首先看一下堆栈跟踪告诉我们NPE发生的位置:

Test

怎么能抛出NPE?

实际上只有一种方法:只有当int length = foo.length(); // HERE 具有值foo时才会发生这种情况。然后,我们尝试在null和.... BANG!

上运行length()方法

但是(我听到你说)如果NPE被抛到null方法调用中会怎么样?

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

Test.java来自哪里?在这种情况下,很明显,我们需要做些什么来解决它。 (为null指定一个非空值。)

好的,让我们尝试一个稍微棘手的例子。这将需要一些逻辑推理

foo

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

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();

看第一行,怎么会抛出一个NPE?有两种方法:

  • 如果int length = test(foo, 1); 的值为bar,则null会抛出NPE。
  • 如果bar[pos]的值为bar[pos],则在其上调用null会抛出NPE。

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

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

那么我们的第二个场景呢?好吧,我们可以看到nullpos,这意味着1必须是foo[1]。这可能吗?

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

null

我们为private static String[] foo = new String[2]; 分配了两个初始化为String[] 的元素。之后,我们尚未更改null的内容...因此foo仍为foo[1]

答案 4 :(得分:408)

就像您正在尝试访问null的对象一样。请考虑以下示例:

TypeA objA;

此时您只是声明此对象,但已初始化或已实例化。每当您尝试访问其中的任何属性或方法时,它都会抛出NullPointerException,这是有道理的。

同样见下面的例子:

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

答案 5 :(得分:347)

当应用程序在需要对象的情况下尝试使用null时,抛出空指针异常。其中包括:

  1. 调用null对象的实例方法。
  2. 访问或修改null对象的字段。
  3. null的长度视为数组。
  4. 访问或修改null的插槽,就好像它是一个阵列一样。
  5. 抛出null,好像它是一个Throwable值。
  6. 应用程序应抛出此类的实例以指示null对象的其他非法使用。

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

答案 6 :(得分:320)

null指针是指向无处的指针。当您取消引用指针p时,您说“在”p“中存储位置的数据。当pnull指针时,存储在p中的位置}是nowhere,你说的是“把这个地方的数据放在'无处''。显然,它不能这样做,所以它会抛出一个null pointer exception

一般情况下,这是因为某些事情尚未正确初始化。

答案 7 :(得分:308)

已经有很多解释来解释它是如何发生以及如何解决的,但您还应该遵循最佳做法来完全避免NullPointerException

另见: A good list of best practices

我想补充一点,非常重要的是,要好好利用final修饰符。 Using the "final" modifier whenever applicable in Java

<强>要点:

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

答案 8 :(得分:302)

空指针异常是指在没有初始化的情况下使用对象的指示符。

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

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

答案 9 :(得分:300)

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

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

  1. 声明
  2. 初始化
  3. 示例:

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

    数组概念相同:

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

    如果您没有给出初始化部分,那么NullPointerException就会出现。

答案 10 :(得分:296)

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

当您尝试执行一个对象方法时,引用会要求生命对象执行该方法。但是如果引用引用NULL(没有,零,无效,nada),则该方法无法执行。然后运行时通过抛出NullPointerException来告诉您。

您的引用是“指向”null,因此“Null - &gt; 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将释放此对象使用的内存并分配另一个。

答案 11 :(得分:273)

当一个声明一个对象数组,然后立即尝试取消引用它内部的元素时,会出现另一个NullPointerException

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

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

数组are initialized to their common initial value内的所有元素;对于任何类型的对象数组,这意味着所有元素都是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));
}