当我运行我的应用程序时,有时会出现一个错误,看起来像这样:
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)
人们把这种错误信息称为“堆栈跟踪”。什么是堆栈跟踪?它如何告诉我程序中发生的错误是什么?
关于这个问题 - 我经常看到一个新手程序员“遇到错误”,他们只粘贴堆栈跟踪和一些随机代码块,而不理解堆栈跟踪是什么或如何使用它。这个问题是针对可能需要帮助理解堆栈跟踪价值的新手程序员的参考。
简单来说,堆栈跟踪是应用程序在抛出异常时正在执行的方法列表。
简单示例
使用问题中给出的示例,我们可以确定异常在应用程序中的确切位置。让我们看一下堆栈跟踪:
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 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); // 该方法在第22行上抛出一个空指针异常
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”部分。对于这些情况,您通常需要找到“根本原因”,这将是堆栈跟踪中最底层的“Caused by”部分之一。在我们的情况下,它是:
Caused by: java.lang.NullPointerException <-- 根本原因
at com.example.myproject.Book.getId(Book.java:22) <-- 重要行
再次,对于这个异常,我们需要查看Book.java
的第22行,以查看可能导致此处的NullPointerException
的内容。
更复杂的示例,涉及库代码
通常,堆栈跟踪比上面两个示例要复杂得多。以下是一个例子(它很长,但展示了多层链式异常):
javax.servlet.ServletException: 发生了一些错误
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.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1157)
at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:388)
at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:182)
at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:765)
at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:418)
at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
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
在这段示例中,我们主要关注的是查找来自 我们的代码 的方法,这些方法位于 com.example.myproject
包中。从上面的第二个示例(如下所示),我们首先想要查找根本原因,那就是:
Caused by: java.sql.SQLException
然而,该语句下的所有方法调用都是库代码。因此,我们将 向上移动 到它上面的 "Caused by",然后在该 "Caused by" 块中,查找来自 我们代码的 第一个方法调用,即:
at com.example.myproject.MyEntityService.save(MyEntityService.java:59)
像在前面的示例中一样,我们应该查看 MyEntityService.java
的第59行,因为错误就源于那里(这个错误有点明显,因为 SQLException 说明了错误,但是调试过程是我们的目标)。