提问人: 提问时间:10/20/2008 最后编辑:Ziggy 更新时间:9/8/2022 访问量:4079266
什么是 NullPointerException,如何修复它?
What is a NullPointerException, and how do I fix it?
问:
什么是空指针异常 (),是什么原因导致的?java.lang.NullPointerException
可以使用哪些方法/工具来确定原因,以便阻止异常导致程序过早终止?
答:
NullPointerException
s 是当您尝试使用指向内存中未指向任何位置 (null) 的引用时发生的异常,就好像它引用了对象一样。对 null 引用调用方法或尝试访问 null 引用的字段将触发 .这些是最常见的方法,但 NullPointerException
javadoc 页面上列出了其他方法。NullPointerException
我能想出的最快的示例代码可能是:NullPointerException
public class Example {
public static void main(String[] args) {
Object obj = null;
obj.hashCode();
}
}
在里面的第一行,我显式地将引用设置为等于 。这意味着我有一个引用,但它没有指向任何对象。之后,我尝试通过调用对象的方法将引用视为指向对象。这会导致 因为在引用所指向的位置没有要执行的代码。main
Object
obj
null
NullPointerException
(这是一个技术问题,但我认为值得一提:指向 null 的引用与指向无效内存位置的 C 指针不同。空指针实际上不指向任何位置,这与指向恰好无效的位置略有不同。
评论
null
这就像您正在尝试访问一个对象,该对象是 .请看下面的例子:null
TypeA objA;
此时,您刚刚声明了此对象,但尚未初始化或实例化。每当您尝试访问其中的任何属性或方法时,它都会抛出有意义的内容。NullPointerException
另请参阅以下示例:
String a = null;
System.out.println(a.toString()); // NullPointerException will be thrown
评论
指针是无处可指的指针。当您取消引用指针时,您会说“给我存储在 ”p“ 中的位置的数据。当是指针时,存储的位置是 ,您说的是“给我'无处'位置的数据”。显然,它不能这样做,所以它抛出了一个.null
p
p
null
p
nowhere
null pointer exception
一般来说,这是因为某些东西没有被正确初始化。
评论
Java 中有两种主要的变量类型:
基元:包含数据的变量。如果要操作基元变量中的数据,可以直接操作该变量。按照惯例,基元类型以小写字母开头。例如,或类型的变量是基元。
int
char
引用:包含内存地址的变量,即引用 .如果要操作引用变量所引用的变量,则必须取消引用它。取消引用通常需要用于访问方法或字段,或者用于索引数组。按照惯例,引用类型通常用以大写开头的类型表示。例如,类型的变量是引用。
Object
Object
Object
.
[
Object
请考虑以下代码,其中声明了基元类型的变量,并且不对其进行初始化:int
int x;
int y = x + x;
这两行将使程序崩溃,因为没有指定任何值,我们尝试使用 的值来指定 。在操作之前,必须将所有基元初始化为可用值。x
x
y
现在事情变得有趣了。引用变量可以设置为“我什么都没引用”。如果您以这种方式显式设置引用变量,或者引用变量未初始化并且编译器无法捕获它,则可以在引用变量中获取值(Java 会自动将该变量设置为 )。null
null
null
如果引用变量由您显式或通过 Java 自动设置为 null,并且您尝试取消引用它,则会得到一个 .NullPointerException
(NPE) 通常发生在声明变量但在尝试使用变量内容之前未创建对象并将其分配给变量时。所以你有一个对实际上不存在的东西的引用。NullPointerException
采用以下代码:
Integer num;
num = new Integer(10);
第一行声明了一个名为 的变量,但它实际上还不包含引用值。由于您还没有说出要指向什么,因此 Java 将其设置为 .num
null
在第二行中,关键字用于实例化(或创建)类型为 的对象,并将引用变量分配给该对象。new
Integer
num
Integer
如果在创建对象之前尝试取消引用,则会得到一个 .在最微不足道的情况下,编译器会捕获问题并让你知道 “,”,但有时你可能会编写不直接创建对象的代码。num
NullPointerException
num may not have been initialized
例如,您可能有如下方法:
public void doSomething(SomeObject obj) {
// Do something to obj, assumes obj is not null
obj.myMethod();
}
在这种情况下,您不是在创建对象,而是假设它是在调用方法之前创建的。请注意,可以像这样调用该方法:obj
doSomething()
doSomething(null);
在这种情况下,是 ,并且该语句将抛出一个 .obj
null
obj.myMethod()
NullPointerException
如果该方法打算像上述方法一样对传入的对象执行某些操作,则抛出 是合适的,因为这是程序员错误,程序员将需要该信息进行调试。NullPointerException
除了由于方法的逻辑而引发的 s 之外,还可以检查方法参数中的值,并通过在方法开头附近添加如下内容来显式引发 NPE:NullPointerException
null
// Throws an NPE with a custom error message if obj is null
Objects.requireNonNull(obj, "obj must not be null");
请注意,在错误消息中明确说明哪个对象不能是 会很有帮助。验证这一点的好处是 1) 您可以返回自己更清晰的错误消息,以及 2) 对于方法的其余部分,您知道除非重新分配,否则它不是 null,并且可以安全地取消引用。null
obj
或者,在某些情况下,该方法的目的不仅仅是对传入的对象进行操作,因此 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 引用会引发 .如果您感到困惑,这里谈论的是限定的超类构造函数调用:
super
NullPointerException
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。NullPointerException
foo
foo.new SomeInnerClass()
抛出 when 为 null。NullPointerException
foo
方法引用 或 在计算时抛出 或 计算结果为 null。
name1::name2
primaryExpression::name
NullPointerException
name1
primaryExpression
JLS 的注释在这里说,不会抛出 NPE,因为是静态的,但仍然抛出 NPE!
someInstance.someStaticMethod()
someStaticMethod
someInstance::someStaticMethod
* 请注意,JLS 可能也间接地谈到了 NPE。
评论
int a=b
Integer
NullPointerException
@Nullable
@NotNull
在 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 将释放此对象使用的内存并分配另一个内存。reference
otherReference
在 Java 中,所有内容(不包括原始类型)都以类的形式出现。
如果你想使用任何对象,那么你有两个阶段:
- 宣
- 初始化
例:
- 声明:
Object object;
- 初始化:
object = new Object();
阵列概念也是如此:
- 声明:
Item item[] = new Item[5];
- 初始化:
item[0] = new Item();
如果你没有给出初始化部分,那么就会出现。NullPointerException
评论
当应用程序在需要对象的情况下尝试使用 null 时,将引发 null 指针异常。这些包括:
- 调用对象的实例方法。
null
- 访问或修改对象的字段。
null
- 将 的长度视为数组。
null
- 访问或修改插槽,就好像它是数组一样。
null
- 像 Throwable 值一样抛出。
null
应用程序应引发此类的实例,以指示该对象的其他非法用途。null
参考: http://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html
评论
null
synchronized
null
switch
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");
}
}
}
评论
当一个人声明一个对象数组,然后立即尝试取消引用其中的元素时,就会发生另一个事件。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));
}
评论
Optional
什么是 NullPointerException?
一个好的起点是 JavaDocs。他们涵盖了以下内容:
当应用程序尝试使用 null 时引发,如果 object 是必需的。这些包括:
- 调用 null 对象的实例方法。
- 访问或修改 null 对象的字段。
- 将 null 的长度视为数组。
- 访问或修改 null 的插槽,就好像它是数组一样。
- 抛出 null,就好像它是 Throwable 值一样。
应用程序应抛出此类的实例以指示其他 非法使用 null 对象。
此外,如果您尝试使用 空引用 ,根据 JLS,这也将抛出此异常:synchronized
SynchronizedStatement: synchronized ( Expression ) Block
- 否则,如果 Expression 的值为 null,则抛出 a。
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();
}
}
确定 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,并且对其调用方法会引发异常。我们可以看到,当程序从方法中删除时,程序停止抛出异常。printString
s
length
s.length()
跟踪这些值的来源
接下来检查此值的来源。通过跟踪该方法的调用者,我们看到该方法被传入,并且是 null。s
printString(name)
print()
this.name
跟踪应设置这些值的位置
设置在哪里?在方法中。通过更多的调试,我们可以看到根本没有调用此方法。如果调用了该方法,请确保检查调用这些方法的顺序,并且 set 方法不会在 print 方法之后调用。this.name
setName(String)
这足以给我们一个解决方案:在调用之前添加一个调用。printer.setName()
printer.print()
其他修复
该变量可以具有默认值(并且可以防止将其设置为 null):setName
private String name = "";
or 方法可以检查 null,例如:print
printString
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)。
评论
问题:什么原因导致(NPE)?NullPointerException
您应该知道,Java 类型分为原始类型(、等)和引用类型。Java 中的引用类型允许您使用特殊值,这是 Java 表示“无对象”的方式。boolean
int
null
每当程序尝试使用 a 时,都会在运行时抛出 A,就好像它是真正的引用一样。例如,如果你这样写:NullPointerException
null
public class Test {
public static void main(String[] args) {
String foo = null;
int length = foo.length(); // HERE
}
}
标记为“HERE”的语句将尝试在引用上运行该方法,这将抛出一个 .length()
null
NullPointerException
您可以通过多种方式使用将生成 .事实上,在不引起 NPE 的情况下,您唯一可以做的事情是:null
NullPointerException
null
- 将其分配给引用变量或从引用变量中读取它,
- 将其分配给数组元素或从数组元素中读取它(前提是数组引用本身为非 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”说我们在类的方法中。
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”表示异常被抛出在类中的某一行中,第 4 行将是第二行“at”行。java.lang.String
Test.java
那么这是从哪里来的呢?在这种情况下,这是显而易见的,而且很明显我们需要做些什么来修复它。(将非 null 值分配给 。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?有两种方法:
- 如果值为 is,则将抛出 NPE。
bar
null
bar[pos]
- 如果值为 ,则调用它将抛出 NPE。
bar[pos]
null
length()
接下来,我们需要弄清楚这些场景中的哪一个解释了实际发生的事情。我们将从探索第一个开始:
从何而来?它是方法调用的一个参数,如果我们看一下调用方式,我们可以看到它来自静态变量。此外,我们可以清楚地看到,我们初始化为非 null 值。这足以暂时驳回这一解释。(从理论上讲,其他东西可以改为......但这不会在这里发生。bar
test
test
foo
foo
foo
null
那么我们的第二种情况呢?好吧,我们可以看到 是 ,所以这意味着必须是 。这可能吗?pos
1
foo[1]
null
确实如此!这就是问题所在。当我们像这样初始化时:
private static String[] foo = new String[2];
我们分配 A 有两个初始化为 null
的元素。在那之后,我们没有改变内容......所以仍然会.String[]
foo
foo[1]
null
在Android上呢?
在 Android 上,追踪 NPE 的直接原因要简单一些。异常消息通常会告诉您正在使用的 null 引用的(编译时)类型,以及在引发 NPE 时尝试调用的方法。这简化了查明直接原因的过程。
但另一方面,Android 有一些常见的特定于平台的 NPE 原因。一种很常见的情况是意外返回 .我的建议是搜索有关意外返回值原因的问答。getViewById
null
null
已经有很多解释来解释它是如何发生的以及如何修复它,但你也应该遵循最佳实践来避免 NullPointerException
。
Смотритетакже: 最佳实践的良好列表
我要补充一点,非常重要的一点是,要好好利用修饰符。在 Java 中尽可能使用“final”修饰符final
总结:
- 使用修饰符强制执行良好的初始化。
final
- 避免在方法中返回 null,例如,在适用时返回空集合。
- 使用批注
@NotNull
和@Nullable
- 快速失败并使用断言来避免 null 对象在整个应用程序中传播,而这些对象不应为 null。
- 首先使用与已知对象相等:
if("knownObject".equals(unknownObject)
- 首选 .
valueOf()
toString()
- 使用 null 安全的
StringUtils
方法。StringUtils.isEmpty(null)
- 在方法中使用 Java 8 Optional 作为返回值,Optional 类提供了一种表示可选值而不是空引用的解决方案。
评论
@Nullable
if (obj==null)
评论