Stack Overflow 上 250W 浏览量的一个问题:你对象丢了
在逛 stack overflow 的时候,发现最火的问题竟然是:什么是 nullpointerexception(java.lang.nullpointerexception
),它是由什么原因导致的,有没有好的方法或者工具可以追踪它发生的原因?
真没想到,这个问题浏览的次数多达 250 万次!所以,我想是时候把最高赞的回答整理一下分享出来了。请随我来。
声明引用变量(即对象)时,实际上是创建了一个指向对象的指针。请看以下代码:
int x; x = 10;
第一行代码声明了一个名为 x 的变量(int 类型),java 会把它初始化为 0。第二行代码把 x 赋值为 10,意味着 10 将被写入到 x 所指向的内存位置上。
但是呢,当我们尝试声明一个引用类型时,情况将会有所不同。
integer num; num = new integer(10);
第一行代码声明了一个名为 num 的变量(integer 类型),java 把它初始化为 null,表示“什么都没有指向 ”。
第二行代码中,new 关键字创建了一个 integer 类型的对象,并将变量 num 指向该对象。
当我们声明了一个变量,却没有将该变量指向任何创建的对象,然后就使用它的时候,nullpointerexception 就发生了。大多数情况下,编译器会发现这个问题,并且提醒我们“xxxx may not have been initialized”。
假如有这样一段代码:
public void dosomething(someobject obj) { //do something to obj }
在这种情况下,我们没有创建对象 obj,而是假设它在 dosomething()
方法被调用之前就创建了。
现在假设在此之前它没有创建。我们这样调用 dosomething()
方法:
dosomething(null);
这就意味着 dosomething()
方法的参数 obj 为 null。如果该方法还要使用 obj 继续做点什么,最好提前抛出 nullpointerexception
,因为开发者需要该信息来进行调试。
还有另外一种替代方法,判断 obj 是不是 null,如果是,就小心行事,做某些不会引起 nullpointerexception 的事情;如果不是,就放心大胆地做该做的事情。
/** * @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,该怎么追踪堆栈信息,找到错误的根源呢?
简单来说,堆栈信息是应用程序在引发 exception 时调用的方法列表,可以准确地定位到错误发生的根源。就像下面这样。
exception in thread "main" java.lang.nullpointerexception at com.example.myproject.book.gettitle(book.java:16) at com.example.myproject.author.getbooktitles(author.java:25) at com.example.myproject.bootstrap.main(bootstrap.java:14)
就上面这个堆栈信息来说,错误发生在“at ...”列表处,第一个“at 处”就是错误最初发生的位置。
at com.example.myproject.book.gettitle(book.java:16)
为了调试,我们可以打开 book.java 类的第 16 行,它可能是:
15 public string gettitle() { 16 system.out.println(title.tostring()); 17 return title; 18 }
从这段代码中可以看得出,错误的原因很可能是因为 title 为 null。
有时候,应用程序会捕获一个异常,然后把它作为另外一种类型的异常抛出。就像下面这样:
34 public void getbookids(int id) { 35 try { 36 book.getid(id); // 这里可能会引发 nullpointerexception 37 } catch (nullpointerexception e) { 38 throw new illegalstateexception("a book has a null property", e) 39 } 40 }
此时的堆栈信息可能是下面这样的:
exception in thread "main" java.lang.illegalstateexception: a book has a null property at com.example.myproject.author.getbookids(author.java:38) at com.example.myproject.bootstrap.main(bootstrap.java:14) caused by: java.lang.nullpointerexception at com.example.myproject.book.getid(book.java:22) at com.example.myproject.author.getbookids(author.java:36) ... 1 more
和之前堆栈信息有所不同的是,这里多了一个“caused by”;有时候还会有更多的“caused by”。在这种情况下,我们通常需要追本溯源,找到最深层次的那个“cause”——它就是堆栈信息中最下面的那个。
caused by: java.lang.nullpointerexception <-- 根本原因 at com.example.myproject.book.getid(book.java:22)
同样,我们需要查看一下 book.java 的第 22 行,找到可能引发 nullpointerexception
的原因。
有时候,堆栈信息要比上面的例子凌乱得多。参考下面这个。
javax.servlet.servletexception: something bad happened at com.example.myproject.opensessioninviewfilter.dofilter(opensessioninviewfilter.java:60) at org.mortbay.jetty.servlet.servlethandler$cachedchain.dofilter(servlethandler.java:1157) at com.example.myproject.exceptionhandlerfilter.dofilter(exceptionhandlerfilter.java:28) at org.mortbay.jetty.servlet.servlethandler$cachedchain.dofilter(servlethandler.java:1157) at com.example.myproject.outputbufferfilter.dofilter(outputbufferfilter.java:33) at org.mortbay.jetty.server.handle(server.java:326) at org.mortbay.jetty.httpconnection.handlerequest(httpconnection.java:542) at org.mortbay.jetty.httpconnection$requesthandler.content(httpconnection.java:943) at org.mortbay.jetty.httpparser.parsenext(httpparser.java:756) at org.mortbay.jetty.httpparser.parseavailable(httpparser.java:218) at org.mortbay.jetty.httpconnection.handle(httpconnection.java:404) at org.mortbay.jetty.bio.socketconnector$connection.run(socketconnector.java:228) at org.mortbay.thread.queuedthreadpool$poolthread.run(queuedthreadpool.java:582) caused by: com.example.myproject.myprojectservletexception at com.example.myproject.myservlet.dopost(myservlet.java:169) at javax.servlet.http.httpservlet.service(httpservlet.java:727) at javax.servlet.http.httpservlet.service(httpservlet.java:820) at org.mortbay.jetty.servlet.servletholder.handle(servletholder.java:511) at org.mortbay.jetty.servlet.servlethandler$cachedchain.dofilter(servlethandler.java:1166) at org.hibernate.persister.entity.abstractentitypersister.insert(abstractentitypersister.java:2822) at org.hibernate.action.entityidentityinsertaction.execute(entityidentityinsertaction.java:71) at org.hibernate.engine.actionqueue.execute(actionqueue.java:268) at org.hibernate.event.def.abstractsaveeventlistener.performsaveorreplicate(abstractsaveeventlistener.java:321) at org.hibernate.event.def.abstractsaveeventlistener.performsave(abstractsaveeventlistener.java:204) at sun.reflect.delegatingmethodaccessorimpl.invoke(delegatingmethodaccessorimpl.java:25) at java.lang.reflect.method.invoke(method.java:597) at org.hibernate.context.threadlocalsessioncontext$transactionprotectionwrapper.invoke(threadlocalsessioncontext.java:344) at $proxy19.save(unknown source) at com.example.myproject.myentityservice.save(myentityservice.java:59) <-- relevant call (see notes below) at com.example.myproject.myservlet.dopost(myservlet.java:164) ... 32 more caused by: java.sql.sqlexception: violation of unique constraint my_entity_uk_1: duplicate value(s) for column(s) my_column in statement [...] at org.hsqldb.jdbc.util.throwerror(unknown source) at org.hsqldb.jdbc.jdbcpreparedstatement.executeupdate(unknown source) at com.mchange.v2.c3p0.impl.newproxypreparedstatement.executeupdate(newproxypreparedstatement.java:105) at org.hibernate.id.insert.abstractselectingdelegate.performinsert(abstractselectingdelegate.java:57) ... 54 more
这个例子当中的堆栈信息实在是太多了,令人眼花缭乱。如果按照之前提供的方法(堆栈信息中最下面的那个)找最深层次的那个“cause”,它就是:
caused by: java.sql.sqlexception: violation of unique constraint my_entity_uk_1: duplicate value(s) for column(s) my_column in statement [...] at org.hsqldb.jdbc.util.throwerror(unknown source) at org.hsqldb.jdbc.jdbcpreparedstatement.executeupdate(unknown source) at com.mchange.v2.c3p0.impl.newproxypreparedstatement.executeupdate(newproxypreparedstatement.java:105) at org.hibernate.id.insert.abstractselec
但其实它并不是的,因为抛出这个异常的方法调用者属于类库代码(c3p0 类库),所以我们需要往上找异常发生的原因,并且这个异常很可能是由我们自己编写的代码(com.example.myproject
包下)引发的,于是我们找到了这样一段异常信息。
at com.example.myproject.myentityservice.save(myentityservice.java:59)
顺藤摸瓜,看看 myentityservice.java 的第 59 行,它就是引发错误的根本原因。
谢谢大家的阅读,原创不易,喜欢就点个赞,这将是我最强的写作动力。如果你觉得文章对你有所帮助,也蛮有趣的,就关注一下我的公众号,谢谢。