什么是 NullPointerException,如何修复它?

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

提问人: 提问时间:10/20/2008 最后编辑:Ziggy 更新时间:9/8/2022 访问量:4079252

问:

这个问题的答案是社区的努力。编辑现有答案以改进此帖子。它目前不接受新的答案或交互。

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

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

Java NullPointerException

评论


答:

963赞 9 revs, 6 users 63%Bill the Lizard #1

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

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

public class Example {

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

}

在里面的第一行,我显式地将引用设置为等于 。这意味着我有一个引用,但它没有指向任何对象。之后,我尝试通过调用对象的方法将引用视为指向对象。这会导致 因为在引用所指向的位置没有要执行的代码。mainObjectobjnullNullPointerException

(这是一个技术问题,但我认为值得一提:指向 null 的引用与指向无效内存位置的 C 指针不同。空指针实际上不指向任何位置,这与指向恰好无效的位置略有不同。

评论

56赞 mmr 2/20/2009
我理解你在那里写的所有内容,但只是因为我已经编码了一段时间,并且知道什么是“指针”和“引用”(以及什么是空,就此而言)。当我试图深入研究这样的解释时,我的学生会斗鸡眼地看着我,因为没有足够的背景。
28赞 Ilmari Karonen 1/18/2014
在实践中获取 NullPointerException 的更常见方法是忘记将成员变量显式初始化为其他内容,而不是在使用它之前,如下所示。使用局部变量时,编译器会捕获此错误,但在这种情况下不会。也许这会对你的答案有一个有用的补充?null
9赞 Steve Powell 7/7/2016
@EJP我认为你的观点是有效的,所以我更新了答案,使其更清晰,并避免在它的地方说“指向空”。
8赞 Bill the Lizard 7/7/2016
@StevePowell很久以前我就表示过,我不希望我的答案改变。请尊重原作者的意图。
5赞 Steve Powell 7/14/2016
对不起,我正在按照此 stackoverflow 项目顶部的要求“改进答案”(这个问题的答案是协作努力的结果:如果您看到可以改进的地方,只需编辑答案即可改进它!我不同意你的版本更好,IMO @EJB有有效的观点;但是非常欢迎您保持您的答案完好无损,尽管它令人困惑。
453赞 5 revs, 5 users 38%Rakesh Burbure #2

这就像您正在尝试访问一个对象,该对象是 .请看下面的例子:null

TypeA objA;

此时,您刚刚声明了此对象,但尚未初始化或实例化。每当您尝试访问其中的任何属性或方法时,它都会抛出有意义的内容。NullPointerException

另请参阅以下示例:

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

评论

4赞 Vijaya Varma Lanke 2/7/2019
如果我们给出 System.out.println(a.length());NullPointerException 将被抛出,要跳过它,我们可以使用 try catch 块来处理。谢谢
358赞 5 revs, 5 users 50%MrZebra #3

指针是无处可指的指针。当您取消引用指针时,您会说“给我存储在 ”p“ 中的位置的数据。当是指针时,存储的位置是 ,您说的是“给我'无处'位置的数据”。显然,它不能这样做,所以它抛出了一个.nullppnullpnowherenull pointer exception

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

评论

8赞 TheRealChx101 9/9/2018
“NULL 指针是指向无处的指针”我不同意。Null 指针不指向任何位置,而是指向 null 值。
4186赞 33 revs, 30 users 29%Vincent Ramdhanie #4

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

  1. 基元:包含数据的变量。如果要操作基元变量中的数据,可以直接操作该变量。按照惯例,基元类型以小写字母开头。例如,或类型的变量是基元。intchar

  2. 引用:包含内存地址的变量,即引用 .如果要操作引用变量所引用的变量,则必须取消引用它。取消引用通常需要用于访问方法或字段,或者用于索引数组。按照惯例,引用类型通常用以大写开头的类型表示。例如,类型的变量是引用。ObjectObjectObject.[Object

请考虑以下代码,其中声明了基元类型的变量,并且不对其进行初始化:int

int x;
int y = x + x;

这两行将使程序崩溃,因为没有指定任何值,我们尝试使用 的值来指定 。在操作之前,必须将所有基元初始化为可用值。xxy

现在事情变得有趣了。引用变量可以设置为“什么都没引用”。如果您以这种方式显式设置引用变量,或者引用变量未初始化并且编译器无法捕获它,则可以在引用变量中获取值(Java 会自动将该变量设置为 )。nullnullnull

如果引用变量由您显式或通过 Java 自动设置为 null,并且您尝试取消引用它,则会得到一个 .NullPointerException

(NPE) 通常发生在声明变量但在尝试使用变量内容之前未创建对象并将其分配给变量时。所以你有一个对实际上不存在的东西的引用。NullPointerException

采用以下代码:

Integer num;
num = new Integer(10);

第一行声明了一个名为 的变量,但它实际上还不包含引用值。由于您还没有说出要指向什么,因此 Java 将其设置为 .numnull

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

如果在创建对象之前尝试取消引用,则会得到一个 .在最微不足道的情况下,编译器会捕获问题并让你知道 “,”,但有时你可能会编写不直接创建对象的代码。numNullPointerExceptionnum may not have been initialized

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

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

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

doSomething(null);

在这种情况下,是 ,并且该语句将抛出一个 .objnullobj.myMethod()NullPointerException

如果该方法打算像上述方法一样对传入的对象执行某些操作,则抛出 是合适的,因为这是程序员错误,程序员将需要该信息进行调试。NullPointerException

除了由于方法的逻辑而引发的 s 之外,还可以检查方法参数中的值,并通过在方法开头附近添加如下内容来显式引发 NPE:NullPointerExceptionnull

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

请注意,在错误消息中明确说明哪个对象不能是 会很有帮助。验证这一点的好处是 1) 您可以返回自己更清晰的错误消息,以及 2) 对于方法的其余部分,您知道除非重新分配,否则它不是 null,并且可以安全地取消引用。nullobj

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

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

可以使用哪些方法/工具来确定原因,以便您停止 导致程序过早终止的例外情况?

带有查找错误的声纳可以检测 NPE。声纳能否动态捕获JVM导致的空指针异常

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

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

在线程“main”中 java.lang.NullPointerException:无法调用“java.util.List.size()”,因为“list”为空

导致NullPointerException

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

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

  • switch (foo) { ... }(无论是表达式还是语句)可以抛出 when 为 null。NullPointerExceptionfoo

  • foo.new SomeInnerClass()抛出 when 为 null。NullPointerExceptionfoo

  • 方法引用 或 在计算时抛出 或 计算结果为 null。name1::name2primaryExpression::nameNullPointerExceptionname1primaryExpression

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

* 请注意,JLS 可能也间接地谈到了 NPE。

评论

616赞 Boann 7/29/2014
“避免此类异常的最佳方法是,当您没有自己创建对象时,始终检查 null。”如果调用方传递 null,但 null 不是该方法的有效参数,则将异常抛回调用方是正确的,因为这是调用方的错。默默地忽略无效输入并在方法中不执行任何操作是非常糟糕的建议,因为它隐藏了问题。
121赞 Simon Fischer 9/27/2014
我想对这篇文章添加一条评论,解释在使用自动装箱时,即使是对原语的赋值也会导致 NPE:如果 b 是 .在某些情况下,调试起来会很混乱。int a=bInteger
68赞 Sid 4/13/2015
是否可以从 Web 浏览器捕获 Web 应用程序抛出的 NPE?就像它是否会显示在 Web 浏览器的视图页面源中一样。
87赞 Rose 11/11/2015
是的,在对对象调用方法或尝试访问它可能具有的变量之前,请检查该对象是否等于 null。有时,构建代码有助于避免 null 指针异常。例如,当使用常量字符串检查输入字符串时,您应该从常量字符串开始,如下所示: if (“SomeString”.equals(inputString)) {} //即使inputString为null,也不会抛出异常。因此,您可以做很多事情来确保安全。
92赞 Arjan Mels 1/4/2016
避免代码中出现问题的另一种方法是使用 和 注释。以下答案对此有更多信息。虽然这个答案是专门针对 IntelliJ IDE 的,但它也适用于其他工具,就像注释中的 apparanet 一样。(顺便说一句,我不被允许直接编辑这个答案,也许作者可以添加它?NullPointerException@Nullable@NotNull
330赞 4 revs, 4 users 79%OscarRyz #5

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...

这是一件需要知道的重要事情 - 当没有对对象的更多引用时(在上面的示例中,当和两者都指向 null 时),则该对象是“无法访问的”。我们无法使用它,因此此对象已准备好进行垃圾回收,并且在某个时候,VM 将释放此对象使用的内存并分配另一个内存。referenceotherReference

339赞 6 revs, 6 users 51%ashish bhatt #6

在 Java 中,所有内容(不包括原始类型)都以类的形式出现。

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

  1. 初始化

例:

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

阵列概念也是如此:

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

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

评论

6赞 sunhang 7/28/2017
调用实例的方法时经常发生 NullPointerException。例如,如果声明一个引用,但未使其指向任何实例,则在调用其方法时将发生 NullPointerException。如:YourClass ref = null;或 ref = anotherRef;但是 anotherRef 没有指向任何实例 ref.someMethod();它将抛出 NullPointerException。一般是这样修复的:在调用方法之前,确定引用是否为空。如: if (yourRef != null) { yourRef.someMethod();
5赞 sunhang 7/28/2017
或者使用异常捕获: 比如: try { yourRef.someMethod(); } catch (NullPointerException e) { // 待办事项 }
380赞 5 revs, 4 users 55%nathan1138 #7

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

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

应用程序应引发此类的实例,以指示该对象的其他非法用途。null

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

评论

15赞 Emiliano 3/23/2016
保持简单,我喜欢这个答案,如果您认为正确,请添加此答案 - 访问对象的未初始化属性
8赞 Stephen C 5/19/2016
@Emiliano - 仅访问初始化的属性不会导致 NPE。正是您>>执行的操作<<使用未初始化的属性值导致了 NPE。
4赞 Stephen C 10/23/2017
如果您想要更多情况:1) 使用 a 作为块的目标,2) 使用 a 作为 的目标,然后拆箱。nullsynchronizednullswitchnull
340赞 6 revs, 5 users 58%javid piprani #8

空指针异常表示您正在使用对象而未对其进行初始化。

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

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

评论

12赞 Mysticial 9/24/2013
虽然这是一个很好的例子,但请问它为所有其他答案尚未涵盖的问题增加了什么?
18赞 Adrian Shum 9/24/2013
在这里使用“未初始化”一词是不合适的。您显示的示例实际上是“已初始化”的,并且使用 null 进行初始化。对于未初始化的变量,编译器会向您抱怨。
302赞 2 revs, 2 users 97%Makoto #9

当一个人声明一个对象数组,然后立即尝试取消引用其中的元素时,就会发生另一个事件。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));
}

评论

4赞 Shailendra Singh 7/8/2016
在实例级别(而不是类级别)对未初始化的对象进行操作将导致 NullPointerException。操作需要特定于实例。如果操作是在类级别,则在未初始化的对象上调用静态方法,则不会引发 NullPointerException 异常。即使是基元包装类对象也会抛出 NullPointerException。
5赞 tomj0101 12/4/2017
1. NullPointerException 是一个 RuntimeException,这意味着在程序运行时会出现,编译时不会出现。!:(,但大多数 IDE 都可以帮助您发现这一点。2. 尽量减少在赋值语句中使用关键字“null”。:)参考网址:
5赞 Makoto 12/4/2017
@tomj0101 我完全不清楚你为什么发表这样的评论......但对于您的第二点,之前的模式是返回 null。关键字很好。知道如何防范它至关重要。这提供了一种常见的情况以及缓解它的方法。Optional
8赞 Makoto 5/29/2018
@Shomu:我什至在什么时候建议应该抓住它?
747赞 7 revs, 5 users 89%fgb #10

什么是 NullPointerException?

一个好的起点是 JavaDocs。他们涵盖了以下内容:

当应用程序尝试使用 null 时引发,如果 object 是必需的。这些包括:

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

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

此外,如果您尝试使用 空引用 ,根据 JLS,这也将抛出此异常:synchronized

SynchronizedStatement:
    synchronized ( Expression ) Block
  • 否则,如果 Expression 的值为 null,则抛出 a。NullPointerException

我该如何解决?

所以你有一个.你如何解决它?让我们举一个简单的例子,它抛出一个:NullPointerExceptionNullPointerException

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

确定 null 值

第一步是准确确定导致异常的值。为此,我们需要做一些调试。学习读取堆栈跟踪非常重要。这将显示抛出异常的位置:

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

跟踪这些值的来源

接下来检查此值的来源。通过跟踪该方法的调用者,我们看到该方法被传入,并且是 null。sprintString(name)print()this.name

跟踪应设置这些值的位置

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

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

其他修复

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

private String name = "";

or 方法可以检查 null,例如:printprintString

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

另请参阅:

我仍然找不到问题

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

评论

20赞 fgb 12/10/2015
@RuchirBaronia 调试器允许您逐行单步执行程序,以查看调用了哪些方法以及如何更改变量。IDE 应该有一些工具可以做到这一点。例如,请参阅 vogella.com/tutorials/EclipseDebugging/article.html
20赞 fgb 12/10/2015
@RuchirBaronia 在堆栈跟踪中所示的任何 NullPointerExceptions 周围的方法上设置断点,并根据预期的值检查变量的值。如果知道某个变量不应该为 null,则可以在更改该值的任何代码周围设置断点。您还可以使用条件断点,这些断点将告诉您值何时更改。
7赞 Massimo 6/30/2017
我还建议使用静态分析工具,例如 FINDBUGS en.m.wikipedia.org/wiki/FindBugs
550赞 12 revs, 7 users 85%Stephen C #11

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

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

每当程序尝试使用 a 时,都会在运行时抛出 A,就好像它是真正的引用一样。例如,如果你这样写:NullPointerExceptionnull

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

标记为“HERE”的语句将尝试在引用上运行该方法,这将抛出一个 .length()nullNullPointerException

您可以通过多种方式使用将生成 .事实上,在不引起 NPE 的情况下,您唯一可以做的事情是:nullNullPointerExceptionnull

  • 将其分配给引用变量或从引用变量中读取它,
  • 将其分配给数组元素或从数组元素中读取它(前提是数组引用本身为非 null!
  • 将其作为参数传递或作为结果返回,或者
  • 使用 OR 运算符或 .==!=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)

这告诉我们很多事情:

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

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

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

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

Смотритетакже: 什么是堆栈跟踪,如何使用它来调试应用程序错误?

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

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

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

让我们先用一个简单的例子(上面)来说明。我们首先看一下堆栈跟踪告诉我们的那条线,即 NPE 发生的位置:

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

这怎么能抛出NPE?

其实,只有一种方法:只有当有价值时,它才能发生。然后,我们尝试运行该方法,然后...砰!foonulllength()null

但是(我听到你说)如果 NPE 被扔在方法调用中怎么办?length()

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

那么这是从哪里来的呢?在这种情况下,这是显而易见的,而且很明显我们需要做些什么来修复它。(将非 null 值分配给 。nullfoo

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

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?有两种方法:

  • 如果值为 is,则将抛出 NPE。barnullbar[pos]
  • 如果值为 ,则调用它将抛出 NPE。bar[pos]nulllength()

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

从何而来?它是方法调用的一个参数,如果我们看一下调用方式,我们可以看到它来自静态变量。此外,我们可以清楚地看到,我们初始化为非 null 值。这足以暂时驳回这一解释。(从理论上讲,其他东西可以为......但这不会在这里发生。bartesttestfoofoofoonull

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

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

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

我们分配 A 有两个初始化为 null 的元素。在那之后,我们没有改变内容......所以仍然会.String[]foofoo[1]null

在Android上呢?

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

但另一方面,Android 有一些常见的特定于平台的 NPE 原因。一种很常见的情况是意外返回 .我的建议是搜索有关意外返回值原因的问答。getViewByIdnullnull

347赞 8 revs, 7 users 48%L. G. #12

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

Смотритетакже: 最佳实践的良好列表

我要补充一点,非常重要的一点是,要好好利用修饰符。在 Java 中尽可能使用“final”修饰符final

总结:

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

评论

7赞 Amaresh Pattanayak 4/17/2015
在j2ee项目中,Nullpointer异常很常见。在某些情况下,引用变量的值为 null。因此,您应该正确检查变量初始化。在条件语句期间,您应该始终检查标志或引用是否包含 null 或不如下所示:- if(flag!=0) { 使用标志的代码 }
18赞 Jan Chimiak 3/5/2016
值得一提的是,一些 IDE(例如 Eclipse)提供了基于可自定义注解的自动无效分析(例如 如上所列),并警告潜在的错误。也可以根据现有的代码结构推断和生成这样的注释(例如,IntelliJ 可以做到这一点)。@Nullable
7赞 Lakmal Vithanage 2/17/2017
首先应该做的是,在使用可为 null 的对象之前,您应该检查它是否为 null,使用 .如果它为 null,那么您也应该编写代码来处理它。if (obj==null)
7赞 L. G. 2/27/2017
IMO,最好尽可能避免在方法中返回 null 对象,并在不允许 null 输入参数时使用注释,以便根据合同减少代码中“if (obj==null)”的数量并提高代码的可读性。
7赞 Stephen C 5/21/2017
读这个...在你接受这些“最佳实践”作为真理之前: satisfice.com/blog/archives/27