在逛 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 进阶资料,嘘。