在逛 Stack Overflow 的時候,發現最火的問題居然是:什麼是 NullPointerException(java.lang.NullPointerException
),它是由什麼緣由致使的,有沒有好的方法或者工具能夠追蹤它發生的緣由?java
真沒想到,這個問題瀏覽的次數多達 250 萬次!因此,我想是時候把最高讚的回答整理一下分享出來了。請隨我來。sql
聲明引用變量(即對象)時,其實是建立了一個指向對象的指針。請看如下代碼:bash
int x;
x = 10;
複製代碼
第一行代碼聲明瞭一個名爲 x 的變量(int 類型),Java 會把它初始化爲 0。第二行代碼把 x 賦值爲 10,意味着 10 將被寫入到 x 所指向的內存位置上。app
可是呢,當咱們嘗試聲明一個引用類型時,狀況將會有所不一樣。工具
Integer num;
num = new Integer(10);
複製代碼
第一行代碼聲明瞭一個名爲 num 的變量(Integer 類型),Java 把它初始化爲 null,表示「什麼都沒有指向 」。spa
第二行代碼中,new 關鍵字建立了一個 Integer 類型的對象,並將變量 num 指向該對象。hibernate
當咱們聲明瞭一個變量,卻沒有將該變量指向任何建立的對象,而後就使用它的時候,NullPointerException 就發生了。大多數狀況下,編譯器會發現這個問題,而且提醒咱們「xxxx may not have been initialized」。指針
假若有這樣一段代碼:調試
public void doSomething(SomeObject obj) {
//do something to obj
}
複製代碼
在這種狀況下,咱們沒有建立對象 obj,而是假設它在 doSomething()
方法被調用以前就建立了。code
如今假設在此以前它沒有建立。咱們這樣調用 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 行,它就是引起錯誤的根本緣由。
謝謝你們的閱讀,原創不易,喜歡就點個贊,這將是我最強的寫做動力。若是你以爲文章對你有所幫助,也蠻有趣的,就關注一下個人公衆號,謝謝。
PS:偷偷地告訴你,後臺回覆「Java」還可領取價值 399 元的 Java 進階資料,噓。