声明引用变量(即对象)时,实际上是在创建指向对象的指针 . 请考虑以下代码,您可以在其中声明基本类型 int 的变量:
int x;
x = 10;
在此示例中,变量x是 int ,Java将为您初始化为0 . 当您在第二行中将其分配给10时,您的值10将被写入x指向的内存位置 .
但是,当您尝试声明引用类型时会发生不同的事情 . 请使用以下代码:
Integer num;
num = new Integer(10);
第一行声明一个名为 num 的变量,但它不包含原始值 . 相反,它包含一个指针(因为类型是 Integer ,它是一个引用类型) . 既然你还没有说什么指向Java将其设置为null,意味着“ I am pointing at nothing ” .
在第二行中, new 关键字用于实例化(或创建)Integer类型的对象,并为指针变量 num 分配此对象 . 您现在可以使用解除引用运算符 . (一个点)来引用该对象 .
您询问的 Exception 在声明变量但未创建对象时发生 . 如果您尝试取消引用 num 在创建对象之前,您将获得 NullPointerException . 在最琐碎的情况下,编译器将捕获问题并让您知道"num may not have been initialized"但有时您编写的代码不会直接创建对象 .
例如,您可能有如下方法:
public void doSomething(SomeObject obj) {
//do something to obj
}
/**
* @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
}
}
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)
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 的值为 null 则 bar[pos] 将抛出NPE .
如果 bar[pos] 的值为 null ,则在其上调用 length() 将抛出NPE .
接下来,我们需要弄清楚哪些场景解释了实际发生的情况 . 我们将从探索第一个开始:
bar 来自哪里?它是 test 方法调用的参数,如果我们看一下如何调用 test ,我们可以看到它来自 foo 静态变量 . 此外,我们可以清楚地看到我们将 foo 初始化为非空值 . 这足以暂时驳回这一解释 . (理论上,其他东西可能会改变 foo ......但这不会发生在这里 . )
NULL 指针是指向无处的指针 . 当您取消引用指针 p 时,您说"give me the data at the location stored in " p“ . 当 p 是空指针时,存储在 p 中的位置是 nowhere ,您执行此操作,因此它会抛出 NULL pointer exception .
一般来说,这是因为某些东西尚未正确初始化 .
256
已经有很多解释来解释它是如何发生的以及如何解决它,但你也应该遵循 best practices 以避免 NullPointerException .
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...
12 回答
声明引用变量(即对象)时,实际上是在创建指向对象的指针 . 请考虑以下代码,您可以在其中声明基本类型
int
的变量:在此示例中,变量x是
int
,Java将为您初始化为0 . 当您在第二行中将其分配给10时,您的值10将被写入x指向的内存位置 .但是,当您尝试声明引用类型时会发生不同的事情 . 请使用以下代码:
第一行声明一个名为
num
的变量,但它不包含原始值 . 相反,它包含一个指针(因为类型是Integer
,它是一个引用类型) . 既然你还没有说什么指向Java将其设置为null,意味着“ I am pointing at nothing ” .在第二行中,
new
关键字用于实例化(或创建)Integer类型的对象,并为指针变量num
分配此对象 . 您现在可以使用解除引用运算符.
(一个点)来引用该对象 .您询问的
Exception
在声明变量但未创建对象时发生 . 如果您尝试取消引用num
在创建对象之前,您将获得NullPointerException
. 在最琐碎的情况下,编译器将捕获问题并让您知道"num may not have been initialized"但有时您编写的代码不会直接创建对象 .例如,您可能有如下方法:
在这种情况下,您不是在创建对象
obj
,而是假设它是在调用doSomething
方法之前创建的 . 不幸的是,可以像这样调用方法:在这种情况下
obj
为空 . 如果该方法旨在对传入的对象执行某些操作,则抛出NullPointerException
是合适的,因为它是程序员错误,程序员将需要该信息用于调试目的 .或者,可能存在这样的情况:该方法的目的不仅仅是对传入的对象进行操作,因此可以接受空参数 . 在这种情况下,您需要检查 null parameter 并采取不同的行为 . 您还应该在文档中解释这一点 . 例如,
doSomething
可以写成:最后,How to pinpoint the exception & cause using Stack Trace
NullPointerException
是当您尝试使用指向内存中没有位置的引用(null)时发生的异常,就好像它引用了一个对象一样 . 在空引用上调用方法或尝试访问空引用的字段将触发NullPointerException
. 这些是最常见的,但NullPointerException javadoc页面上列出了其他方式 .可能是我能想出的最快的示例代码来说明
NullPointerException
:在
main
内的第一行,我明确地将Object
引用obj
设置为等于null
. 这意味着我有一个引用,但它没有指向任何对象 . 之后,我尝试将引用视为通过调用其上的方法指向对象 . 这导致NullPointerException
因为没有代码在引用指向的位置执行 .(这是技术性的,但我认为值得一提的是:指向null的引用与指向无效内存位置的C指针不同 . 空指针实际上并不指向任何位置,这与指向恰好无效的位置 . )
什么是NullPointerException?
一个好的起点是JavaDocs . 他们有这个涵盖:
如果您尝试使用带有
synchronized
的空引用,也会抛出此异常,per the JLS:如何解决?
所以你有
NullPointerException
. 你是如何解决的?我们来看一个抛出NullPointerException
的简单示例:Identify the null values
第一步是确切地确定导致异常的值 . 为此,我们需要做一些调试 . 学习阅读堆栈跟踪很重要 . 这将显示抛出异常的位置:
在这里,我们看到了例外在第13行抛出(在
printString
方法中) . 查看该行并通过添加日志记录语句或使用调试器来检查哪些值为空 . 我们发现s
为null,并且在其上调用length
方法会抛出异常 . 我们可以看到,当从方法中删除s.length()
时,程序停止抛出异常 .Trace where these values come from
接下来检查此值的来源 . 通过遵循方法的调用者,我们看到
s
在print()
方法中使用printString(name)
传入,this.name
为null .Trace where these values should be set
this.name
在哪里设置?在setName(String)
方法中 . 通过一些更多的调试,我们可以看到根本没有调用此方法 . 如果调用该方法,请确保检查调用这些方法的顺序,并且在print方法之后不调用set方法 .这足以给我们一个解决方案:在调用
printer.print()
之前添加对printer.setName()
的调用 .其他修复
变量可以有一个默认值(
setName
可以防止它被设置为null):print
或printString
方法可以检查null,例如:或者您可以设计类,以便
name
始终具有非null值:See also:
我仍然找不到问题
如果你试图调试问题,但仍然没有尝试到目前为止 . 问题中至少为 include the stacktrace ,代码中为 mark the important line numbers . 另外,请先尝试简化代码(请参阅SSCCE) .
问题:导致NullPointerException(NPE)的原因是什么?
您应该知道,Java类型分为基本类型(
boolean
,int
等)和引用类型 . Java中的引用类型允许您使用特殊值null
,这是Java方式"no object" .每当程序尝试使用
null
时,就会在运行时抛出NullPointerException
,就像它是真正的引用一样 . 例如,如果你这样写:标记为"HERE"的语句将尝试在
null
引用上运行length()
方法,这将抛出NullPointerException
.有很多方法可以使用
null
值,这将导致NullPointerException
. 事实上,在没有造成NPE的情况下,你可以用_657407做的唯一事情是:将其分配给引用变量或从引用变量中读取它,
将其分配给数组元素或从数组元素中读取它(假设数组引用本身为非null!),
将其作为参数传递或作为结果返回,或者
使用
==
或!=
运算符或instanceof
对其进行测试 .问题:如何阅读NPE堆栈跟踪?
假设我编译并运行上面的程序:
首先观察:编译成功!程序中的问题不是编译错误 . 这是一个运行时错误 . (某些IDE可能会警告您的程序将始终抛出异常......但标准的
javac
编译器不会 . )第二个观察:当我运行程序时,它会输出两行"gobbledy-gook" . WRONG!! 那不是狼吞虎咽的 . 它是一个堆栈跟踪...它提供了重要的信息,可以帮助您跟踪代码中的错误,如果您花时间仔细阅读它 .
那么让我们来看看它的内容:
堆栈跟踪的第一行告诉您许多事情:
它告诉您抛出异常的Java线程的名称 . 对于一个带有一个线程的简单程序(比如这个),它将是"main" . 让我们继续 ...
它告诉你抛出的异常的全名;即
java.lang.NullPointerException
.如果异常有关联的错误消息,则将在异常名称后输出 .
NullPointerException
在这方面很不寻常,因为它很少有错误信息 .第二行是诊断NPE中最重要的一行 .
这告诉了我们许多事情:
"at Test.main"说我们在
Test
类的main
方法中 ."Test.java:4"给出了类的源文件名,它告诉我们发生这种情况的语句在文件的第4行 .
如果你计算上面文件中的行,第4行是我用“HERE”注释标记的行 .
请注意,在一个更复杂的示例中,NPE堆栈跟踪中会有很多行 . 但是你可以确定第二行(第一行"at"行)会告诉你NPE被抛出的位置1 .
简而言之,堆栈跟踪将毫不含糊地告诉我们程序的哪个语句抛出了NPE .
1 - 不完全正确 . 有些东西叫做嵌套异常......
问题:如何在代码中追踪NPE异常的原因?
这是困难的部分 . 简短的回答是对堆栈跟踪,源代码和相关API文档提供的证据应用逻辑推理 .
让我们首先用简单的例子(上面)来说明 . 我们首先看一下堆栈跟踪告诉我们NPE发生的位置:
怎么样能扔掉NPE吗?
实际上只有一种方法:只有当
foo
的值为null
时才会发生 . 然后我们尝试在null
上运行length()
方法并且....砰!但是(我听你说)如果NPE被抛入
length()
方法调用中怎么办?好吧,如果发生这种情况,堆栈跟踪看起来会有所不同 . 第一个"at"行会在
java.lang.String
类的某行中抛出异常,Test.java
的第4行将是第二个"at"行 .那么
null
来自哪里?在这种情况下,很明显,我们需要做的是修复它 . (为foo
指定非空值 . )好的,让我们尝试一个稍微棘手的例子 . 这将需要一些逻辑演绎 .
所以现在我们有两条“at”线 . 第一个是这一行:
第二个就是这一行:
看第一行,怎么会抛出一个NPE?有两种方法:
如果
bar
的值为null
则bar[pos]
将抛出NPE .如果
bar[pos]
的值为null
,则在其上调用length()
将抛出NPE .接下来,我们需要弄清楚哪些场景解释了实际发生的情况 . 我们将从探索第一个开始:
bar
来自哪里?它是test
方法调用的参数,如果我们看一下如何调用test
,我们可以看到它来自foo
静态变量 . 此外,我们可以清楚地看到我们将foo
初始化为非空值 . 这足以暂时驳回这一解释 . (理论上,其他东西可能会改变foo
......但这不会发生在这里 . )那么我们的第二个场景呢?好吧,我们可以看到
pos
是1
,这意味着foo[1]
必须是null
. 那可能吗?的确是!这就是问题所在 . 当我们这样初始化时:
我们用一个初始化为
null
的两个元素分配String[]
. 在那之后,我们没有改变foo
的内容......所以foo[1]
仍然是null
.这就像你试图访问一个
null
的对象 . 考虑下面的例子:此时你只有 declared 这个对象而不是 initialized or instantiated . 每当你尝试访问其中的任何属性或方法时,它都会抛出
NullPointerException
,这是有道理的 .请参阅以下示例:
当应用程序在需要对象的情况下尝试使用null时,将引发空指针异常 . 这些包括:
调用
null
对象的实例方法 .访问或修改
null
对象的字段 .将
null
的长度视为数组 .访问或修改
null
的插槽,就好像它是一个阵列一样 .抛出
null
,好像它是一个Throwable值 .应用程序应抛出此类的实例以指示
null
对象的其他非法用法 .参考:http://docs.oracle.com/javase/8/docs/api/java/lang/NullPointerException.html
NULL
指针是指向无处的指针 . 当您取消引用指针p
时,您说"give me the data at the location stored in " p“ . 当p
是空指针时,存储在p
中的位置是nowhere
,您执行此操作,因此它会抛出NULL pointer exception
.一般来说,这是因为某些东西尚未正确初始化 .
已经有很多解释来解释它是如何发生的以及如何解决它,但你也应该遵循 best practices 以避免
NullPointerException
.另见:A good list of best practices
我想补充一点,非常重要的是,要好好利用
final
修饰符 . Using the "final" modifier whenever applicable in JavaSummary:
使用
final
修饰符强制执行良好的初始化 .避免在方法中返回null,例如在适用时返回空集合 .
使用注释
@NotNull
和@Nullable
快速失败并使用断言以避免在不应为null的情况下将null对象传播到整个应用程序 .
首先使用等于已知对象:
if("knownObject".equals(unknownObject)
首选
valueOf()
over toString() .使用null safe
StringUtils
methodsStringUtils.isEmpty(null)
.空指针异常是指示您正在使用对象而不初始化它 .
例如,下面是一个将在我们的代码中使用它的学生类 .
下面的代码为您提供了空指针异常 .
因为您使用的是
Obj_Student
,但是您忘记将其初始化,就像在下面显示的正确代码中一样:在Java中,一切都是以类的形式出现的 .
如果您想使用任何对象,那么您有两个阶段:
申报
初始化
例:
宣言:
Object a;
初始化:
a=new Object();
对于阵列概念也是如此
宣言:
Item i[]=new Item[5];
初始化:
i[0]=new Item();
如果您没有给出初始化部分,则会出现
NullpointerException
.在Java中,您声明的所有变量实际上都是"references"对象(或基元)而不是对象本身 .
当您尝试执行一个对象方法时,该引用会要求生命对象执行该方法 . 但是如果引用引用NULL(nothing,zero,void,nada),则该方法无法执行 . 然后运行时通过抛出NullPointerException让您知道这一点 .
你的引用是“指向”null,因此“Null - > Pointer” .
该对象位于VM内存空间中,访问它的唯一方法是使用
this
引用 . 举个例子:在代码中的另一个位置:
这是一个重要的事情要知道 - 当没有更多对象的引用时(在上面的例子中
reference
和otherReference
都指向null),那么对象是"unreachable" . 我们无法使用它,因此该对象已准备好进行垃圾收集,并且在某些时候,VM将释放此对象使用的内存并将分配另一个 .当一个声明一个对象数组,然后立即尝试取消引用它内部的元素时,会出现另一个
NullPointerException
.如果比较顺序颠倒,则可以避免这种特殊的NPE;即,在保证的非null对象上使用.equals .
数组内的所有元素are initialized to their common initial value;对于任何类型的对象数组,这意味着所有元素都是
null
.在访问或取消引用它们之前,必须初始化数组中的元素 .