什么是空指针异常(java.lang.NullPointerException
)以及它的产生原因?
有哪些方法/工具可以用来确定它的原因,以便防止该异常导致程序过早终止?
什么是空指针异常(java.lang.NullPointerException
)以及它的产生原因?
有哪些方法/工具可以用来确定它的原因,以便防止该异常导致程序过早终止?
int x;
int y = x + x;
这两行代码会导致程序崩溃,因为没有为x
指定值,但我们试图使用x
的值来指定y
。所有的基本数据类型在操作之前都必须初始化为可用的值。
现在,有一点很有趣。引用变量可以被设置为null
,这意味着“我没有引用任何东西”。你可以通过显式设置或者引用变量未初始化且编译器没有捕获到(Java会自动将变量设置为null
)来得到null
值。
如果引用变量被明确或因Java自动设置为null
,并且你试图对其进行取消引用,那么你就会得到一个NullPointerException
。
NullPointerException
(NPE)通常发生在你声明一个变量但在尝试使用该变量的内容之前没有创建对象并将其赋值给该变量时。所以你有一个对不存在的东西的引用。
看下面的代码:
Integer num;
num = new Integer(10);
第一行声明了一个名为num
的变量,但它实际上还没有包含任何引用值。由于您尚未指定要指向什么,Java将其设置为null
。
在第二行中,使用new
关键字来实例化(或创建)类型为Integer
的对象,并将引用变量num
分配给该Integer
对象。
如果在创建对象之前尝试解引用num
,则会出现NullPointerException
。在最简单的情况下,编译器会捕获此问题并让您知道“num可能尚未初始化
”,但有时您可能会编写不直接创建对象的代码。
例如,您可能有一个如下所示的方法:
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
异常是适当的,因为这是程序员的错误,程序员需要这些信息进行调试。
除了由于方法逻辑而引发的NullPointerException
异常外,还可以检查方法参数是否为null
值,并通过在方法开头添加以下内容来显式抛出NPE:
// Throws an NPE with a custom error message if obj is null
Objects.requireNonNull(obj, "obj must not be null");
请注意,在错误信息中清楚地说明不能为 null
的对象是哪个对象会更有帮助。验证这一点的好处在于:1)您可以返回自己更明确的错误消息;2)对于方法的其余部分,您知道除非重新分配 obj
,否则它不为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
}
}
有哪些方法/工具可用于确定原因,以便停止异常使程序过早终止?
Sonar与FindBugs可以检测NPE。 Sonar能够动态捕获JVM引起的空指针异常吗?
现在Java 14添加了一种新的语言特性来显示NullPointerException的根本原因。这个语言特性自2006年以来就已经是SAP商业JVM的一部分。
在Java 14中,以下是一个NullPointerException异常消息示例:
在线程“main”中,java.lang.NullPointerException: 无法调用“java.util.List.size()”,因为“list”为空
下面是直接*由Java语言规范提到的可能会导致NullPointerException发生的所有情况:
throw null;
synchronized(someNullReference){...}
super
会抛出NullPointerException
。 如果您感到困惑,这是指限定超类构造函数调用:class Outer {
class Inner {}
}
class ChildOfInner extends Outer.Inner {
ChildOfInner(Outer o) {
o.super(); // if o is null, NPE gets thrown
}
}
使用for (element : iterable)
循环来遍历一个空的集合/数组会导致空指针异常。
当foo
为null时,switch (foo) { ... }
(无论是表达式还是语句)会抛出空指针异常。
当foo
为null时,foo.new SomeInnerClass()
会抛出空指针异常。
形如name1::name2
或primaryExpression::name
的方法引用在name1
或primaryExpression
为null时会抛出空指针异常。JLS中有一个注释,表示someInstance.someStaticMethod()
不会抛出NPE,因为someStaticMethod
是静态的,但是someInstance::someStaticMethod
仍然会抛出NPE!
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
以外的其他值。类似于这样:链接。对于局部变量,编译器会捕获此错误,但在这种情况下它不会。也许将此内容添加到您的答案中会很有用? - Ilmari Karonen开始了解最好的方法是查阅JavaDocs文档,其中已经有详细说明:
当应用程序试图在需要对象的情况下使用null时抛出此异常。这些情况包括:
- 调用空对象的实例方法。
- 访问或修改空对象的字段。
- 将null长度视为数组。
- 访问或修改空对象的槽,就像它是一个数组一样。
- 将null作为可抛掷值抛出。
应用程序应该抛出此类的实例以指示null对象的其他非法使用。
如果你尝试将null引用与synchronized
一起使用,也会导致抛出此异常,根据JLS规范也是如此。
SynchronizedStatement: synchronized ( Expression ) Block
- Otherwise, if the value of the Expression is null, a
NullPointerException
is thrown.
你遇到了一个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)
printString
方法中)抛出的。查看该行并检查哪些值为null,可以通过添加日志语句或使用调试器来实现。我们发现s
为null,调用其上的length
方法会引发异常。当从该方法中删除s.length()
时,可以看到程序停止抛出异常。
跟踪这些值来自哪里
接下来检查这个值来自哪里。通过跟踪方法的调用者,我们可以看到s
是通过在print()
方法中传入printString(name)
来传递的,而this.name
为null。
跟踪这些值应该在哪里设置
那么this.name
在哪里设置?在setName(String)
方法中进行设置。经过更多的调试,我们发现根本没有调用这个方法。如果调用了该方法,请确保检查调用这些方法的顺序,并且在调用打印方法之前没有调用设置方法。printer.print()
之前添加对printer.setName()
的调用。
setName
可以防止将其设置为null):private String name = "";
print
或printString
方法都可以检查是否为null,例如:
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表示“没有对象”的方式。
每当您的程序试图将null
用作真实引用时,运行时就会抛出NullPointerException
。例如,如果您编写以下代码:
public class Test {
public static void main(String[] args) {
String foo = null;
int length = foo.length(); // HERE
}
}
标记为“HERE”的语句将尝试在null
引用上运行length()
方法,这将抛出NullPointerException
异常。
有许多方法可以使用null
值,这将导致NullPointerException
异常。实际上,您唯一可以执行的null
操作而不会导致NPE的是:
==
或!=
操作符或instanceof
测试它。假设我编译并运行上面的程序:
$ 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.lang.NullPointerException
。NullPointerException
在这方面是不寻常的,因为它很少有错误消息。第二行是诊断NPE最重要的一行。
at Test.main(Test.java:4)
这告诉我们一些事情:
Test
类的 main
方法中。如果你数一下文件中的行数,第 4 行是我用“HERE”标签标记的那一行。
请注意,在更复杂的例子中,NPE堆栈跟踪中会有很多行。但你可以确定第二行(第一个“at”行)将告诉你 NPE 抛出的位置1。
简而言之,堆栈跟踪将明确告诉我们程序哪个语句抛出了 NPE。
1 - 不完全正确。有一种叫做嵌套异常的东西……
这是困难的部分。简短的答案是,通过对堆栈跟踪、源代码和相关 API 文档提供的证据进行逻辑推理。
让我们首先用简单的例子来说明。我们从堆栈跟踪告诉我们 NPE 发生位置开始:
int length = foo.length(); // HERE
如何会发生NPE呢?只有一种情况:当foo
的值为null
时,我们尝试在null
上运行length()
方法,结果......出现错误!
但是(也许你会问),如果NPE是在length()
方法调用内部抛出的呢?
好吧,如果发生了这种情况,堆栈跟踪将会不同。第一个“at”行将表示异常在java.lang.String
类的某一行中抛出,并且Test.java
的第4行将成为第二个“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(NullPointerException)?有两种情况:
bar
的值为null
,那么bar[pos]
将抛出NPE。bar[pos]
的值是null
,那么在它上调用length()
将抛出NPE。接下来,我们需要找出哪种情况可以解释实际发生了什么。我们将从探究第一种情况开始:
bar
从何而来?它是test
方法调用的参数,如果我们查看如何调用test
,我们可以看到它来自foo
静态变量。此外,我们可以清楚地看到我们将foo
初始化为非空值。这就足以暂时排除这种情况。(理论上,其他东西可能会将foo
更改为null
...但这里没有发生。)
那么我们的第二种情况呢?嗯,我们可以看到pos
是1
,这意味着foo[1]
必须是null
。这是可能的吗?
是可以的!这就是问题所在。当我们像这样初始化时:
private static String[] foo = new String[2];
我们分配了一个包含两个元素的String[]
,这些元素被初始化为null
。此后,我们没有改变foo
的内容...所以foo[1]
仍将是null
。
在Android上,追踪NPE的直接原因会简单一些。异常消息通常会告诉您使用空引用的(编译时)类型以及抛出NPE时尝试调用的方法。这简化了确定直接原因的过程。
但是在另一方面,Android有一些特定于平台的常见NPE原因。一个非常普遍的情况是getViewById
意外返回了null
。我的建议是搜索关于意外null
返回值原因的问题和答案。
好比你在尝试访问一个为null
的对象。考虑下面的示例:
TypeA objA;
此时您只是声明了这个对象,但尚未初始化或实例化。当您尝试访问其中的任何属性或方法时,它将抛出NullPointerException
,这是有道理的。
同时参考下面这个示例:
String a = null;
System.out.println(a.toString()); // NullPointerException will be thrown
当应用程序试图在需要对象的情况下使用null时,将抛出空指针异常。这些情况包括:
应用程序应该抛出这个类的实例来指示对null对象的其他非法使用。
参考:http://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html
null
指针是指向无处的指针。当你引用一个指针p
时,你会说“给我存储在‘p'中的位置上的数据”。当p
是一个null
指针时,“p”所存储的位置是“无处”,你相当于在说“给我在‘无处’位置上的数据”。显然,它做不到这点,因此会抛出一个null指针异常
。NullPointerException
。final
修饰符。
在Java中尽可能使用“final”修饰符
摘要:
final
修饰符来确保良好的初始化。@NotNull
和 @Nullable
。if("knownObject".equals(unknownObject)
valueOf()
而不是 toString()
。StringUtils
方法 StringUtils.isEmpty(null)
。@Nullable
)提供自动空值分析,并警告可能存在的错误。还可以根据现有代码结构推断和生成这样的注解(例如 IntelliJ 可以做到这一点)。 - Jan Chimiakif (obj==null)
进行判断。如果它是空的,则需要编写代码来处理它。请注意不改变原意。 - Lakmal Vithanage空指针异常表示您在未初始化对象的情况下使用它。
例如,以下是一个学生类,将在我们的代码中使用它。
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");
}
}
}
Integer
,那么int a=b
可能会抛出NPE。在某些情况下,这会让调试变得困难和混乱。 - Simon Fischer