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

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

有帮助吗?

解决方案

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

的变量
int x;
x = 10;

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

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

Integer num;
num = new Integer(10);

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

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

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

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

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

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

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

doSomething(null);

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

最后,如何查明异常&导致使用堆栈跟踪

其他提示

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指针不同。空指针实际上并不指向任何地方,与指向恰好无效的位置略有不同。)

什么是空指针异常?

一个好的起点是 Java文档. 。他们涵盖了这个:

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

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

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

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

SynchronizedStatement:
    synchronized ( Expression ) Block
  • 否则,如果表达式的值为 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 为空,并调用 length 其上的方法抛出异常。我们可以看到程序停止抛出异常 s.length() 从方法中删除。

追踪这些值的来源

接下来检查这个值来自哪里。通过跟踪该方法的调用者,我们看到 s 传入的是 printString(name) 在里面 print() 方法,以及 this.name 一片空白。

跟踪应设置这些值的位置

哪里 this.name 放?在里面 setName(String) 方法。通过更多的调试,我们可以看到这个方法根本没有被调用。如果调用了该方法,请务必检查 命令 调用这些方法,并且未调用 set 方法 打印方法。

这足以给我们一个解决方案:添加一个呼叫 printer.setName() 打电话之前 printer.print().

其他修复

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

private String name = "";

要么是 print 或者 printString 方法可以 检查是否为空, , 例如:

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

也可以看看:

我还是找不到问题

如果您尝试调试问题但仍然没有解决方案,您可以发布问题以获取更多帮助,但请确保包含您迄今为止尝试过的内容。最低限度, 包括堆栈跟踪 在问题中,并且 标记重要的行号 在代码中。另外,首先尝试简化代码(请参阅 SSCCE).

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

你应该知道,Java 类型分为 原始类型 (boolean, int, 等)和 参考类型. 。Java中的引用类型允许您使用特殊值 null 这是 Java 中“没有对象”的说法。

A NullPointerException 每当您的程序尝试使用 null 就好像它是一个真正的参考一样。例如,如果你这样写:

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

标记为“HERE”的语句将尝试运行 length() 上的方法 null 引用,这会抛出一个 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”。让我们继续 ...
  • 它告诉您抛出的异常的全名;IE。 java.lang.NullPointerException.
  • 如果异常有关联的错误消息,则会在异常名称之后输出。 NullPointerException 在这方面很不寻常,因为它很少有错误消息。

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

at Test.main(Test.java:4)

这告诉我们很多事情:

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

如果您数一下上面文件中的行数,就会发现第 4 行是我用“HERE”注释标记的行。

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

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

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

问题:如何查找代码中 NPE 异常的原因?

这是最难的部分。简而言之,就是对堆栈跟踪、源代码和相关 API 文档提供的证据进行逻辑推理。

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

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

怎么会抛出 NPE?

事实上只有一种方法:它只有在以下情况下才会发生 foo 有价值 null. 。然后我们尝试运行 length() 方法上 null 和 ....砰!

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

好吧,如果发生这种情况,堆栈跟踪看起来会有所不同。第一个“at”行表示异常是在 java.lang.String 类,以及第 4 行 Test.java 将是第二个“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?有两种方法:

  • 如果值 barnull 然后 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];

我们分配一个 String[] 有两个元素 被初始化为 null. 。之后我们就没有再改变过内容了 foo ...所以 foo[1] 仍将是 null.

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

TypeA objA;

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

同样见下面的例子:

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

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

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

    参考: http://docs.oracle。 COM / JavaSE的/ 8 /文档/ API /爪哇/郎/ NullPointerException.html

null 指针是指向无处的指针。当您取消引用指针 p 时,您说“在p”中存储的位置给我数据。当 p null 指针时,存储在 p 中的位置是 nowhere ,你说的是“给”我在“无处”的位置的数据。显然,它不能这样做,因此抛出空指针异常

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

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

另见: 一个好的清单最佳做法

我想补充一点,非常重要的是,要好好利用 final 修饰符。 使用“最终”版本。适用于Java的修饰符

<强>要点:

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

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

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

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

在Java中,一切(不包括原始类型)都是类的形式。

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

  1. 宣布
  2. 初始化

例子:

  • 宣言: Object object;
  • 初始化: object = new Object();

数组概念相同:

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

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

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

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

您的引用是“指向”为null,因此“Null - &gt;指针&QUOT;

该对象存在于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...

这一点很重要 - 当没有更多对象的引用时(在上面的例子中 reference otherReference 都指向null)那么对象是“无法到达”。我们无法使用它,所以这个对象已准备好被垃圾收集,并且在某些时候,VM将释放该对象使用的内存并分配另一个。

当一个声明一个对象数组,然后立即尝试取消引用它内部的元素时,会发生另一个 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));
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top