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

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


当前回答

什么是NullPointerException?

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

类的情况下,当应用程序试图使用null时引发 对象是必需的。这些包括: 调用空对象的实例方法。 访问或修改空对象的字段。 取null的长度,就好像它是一个数组一样。 访问或修改null的槽位,就像它是一个数组一样。 抛出null,就好像它是一个Throwable值。 应用程序应该抛出该类的实例来指示其他类 null对象的非法使用。

同样的情况是,如果你试图使用synchronized的空引用,也会抛出这个异常,根据JLS:

SynchronizedStatement: synchronized(表达式)块 否则,如果表达式的值为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();
    }
}

识别空值

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

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()从方法中移除时,程序停止抛出异常。

跟踪这些值的来源

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

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

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

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

其他修复

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

private String name = "";

print或printString方法都可以检查null,例如:

printString((name == null) ? "" : 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();
    }
}

参见:

避免”!= null "语句在Java?

我还是找不到问题

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

其他回答

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

可能我能想出的最快的示例代码来说明NullPointerException将是:

public class Example {

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

}

在main内部的第一行,我显式地将对象引用obj设置为null。这意味着我有一个引用,但它不指向任何对象。在此之后,我尝试通过调用对象上的方法来将引用视为指向对象。这将导致NullPointerException,因为在引用所指向的位置中没有代码要执行。

(这是一个技术细节,但我认为值得一提:指向null的引用与指向无效内存位置的C指针不同。空指针字面上不指向任何地方,这与指向一个恰好无效的位置有微妙的不同。)

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

TypeA objA;

此时,您刚刚声明了该对象,但尚未初始化或实例化。无论何时你试图访问其中的任何属性或方法,它都会抛出NullPointerException,这是有意义的。

请看下面的例子:

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

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

您应该知道,Java类型分为基本类型(boolean、int等)和引用类型。Java中的引用类型允许您使用特殊值null,这是Java表示“无对象”的方式。

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

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

标记为“HERE”的语句将尝试在空引用上运行length()方法,这将抛出NullPointerException。

有许多方法可以使用空值,从而导致NullPointerException异常。事实上,你可以在不引起NPE的情况下对null做的唯一事情是:

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

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

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

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

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

第二点观察:当我运行这个程序时,它输出了两行“官样文章”。错了! !这不是官样文章。这是一个堆栈跟踪…如果您花时间仔细阅读它,它提供的重要信息将帮助您追踪代码中的错误。

让我们看看它说了什么:

Exception in thread "main" java.lang.NullPointerException

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

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

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

at Test.main(Test.java:4)

这告诉我们一些事情:

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

如果你计算一下上面文件中的行数,第4行就是我用“HERE”注释标记的那行。

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

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

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

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

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

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

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

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

这怎么能引发NPE呢?

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

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

如果发生了这种情况,堆栈跟踪看起来就不一样了。第一个“at”行表示异常是在java.lang.String类中的某一行中抛出的,Test.java的第4行是第二个“at”行。

那么零是从哪里来的呢?在这种情况下,很明显,我们需要做什么来解决它。(为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的值为空,那么bar[pos]将抛出一个NPE。 如果bar[pos]的值为空,则对其调用length()将抛出一个NPE。

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

bar是从哪里来的?它是test方法调用的参数,如果我们看看test是如何被调用的,我们可以看到它来自foo static变量。此外,我们可以清楚地看到,我们将foo初始化为一个非空值。这足以暂时否定这种解释。(理论上,其他一些东西可以将foo更改为null…但这并没有发生在这里。)

那么第二种情况呢?我们可以看到pos是1,这意味着foo[1]必须为空。这可能吗?

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

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

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

Android系统呢?

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

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

什么是NullPointerException?

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

类的情况下,当应用程序试图使用null时引发 对象是必需的。这些包括: 调用空对象的实例方法。 访问或修改空对象的字段。 取null的长度,就好像它是一个数组一样。 访问或修改null的槽位,就像它是一个数组一样。 抛出null,就好像它是一个Throwable值。 应用程序应该抛出该类的实例来指示其他类 null对象的非法使用。

同样的情况是,如果你试图使用synchronized的空引用,也会抛出这个异常,根据JLS:

SynchronizedStatement: synchronized(表达式)块 否则,如果表达式的值为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();
    }
}

识别空值

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

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()从方法中移除时,程序停止抛出异常。

跟踪这些值的来源

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

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

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

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

其他修复

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

private String name = "";

print或printString方法都可以检查null,例如:

printString((name == null) ? "" : 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();
    }
}

参见:

避免”!= null "语句在Java?

我还是找不到问题

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

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

参见: 一个很好的最佳实践列表

我还要补充一点,很重要的一点是,充分利用最后一个修饰语。 在Java中使用“final”修饰符

简介:

Use the final modifier to enforce good initialization. Avoid returning null in methods, for example returning empty collections when applicable. Use annotations @NotNull and @Nullable Fail fast and use asserts to avoid propagation of null objects through the whole application when they shouldn't be null. Use equals with a known object first: if("knownObject".equals(unknownObject) Prefer valueOf() over toString(). Use null safe StringUtils methods StringUtils.isEmpty(null). Use Java 8 Optional as return value in methods, Optional class provide a solution for representing optional values instead of null references.