有时当我运行我的应用程序时,它会给我一个错误,看起来像这样:

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)

人们将其称为“堆栈跟踪”。什么是堆栈跟踪?关于程序中发生的错误,它能告诉我什么?


关于这个问题,我经常看到一个新手程序员“得到一个错误”,他们只是简单地粘贴他们的堆栈跟踪和一些随机的代码块,而不了解堆栈跟踪是什么或如何使用它。此问题旨在作为新手程序员的参考,他们可能需要帮助来理解堆栈跟踪的值。


当前回答

其他文章描述了什么是堆栈跟踪,但它仍然很难使用。

如果您获得堆栈跟踪,并希望跟踪异常的原因,那么理解它的一个良好起点是使用Eclipse中的Java堆栈跟踪控制台。如果您使用另一个IDE,可能会有类似的特性,但这个答案是关于Eclipse的。

首先,确保在Eclipse项目中可以访问所有Java源代码。

然后在Java透视图中,单击Console选项卡(通常在底部)。如果控制台视图不可见,进入菜单选项“Window -> Show view”,选择“Console”。

然后在控制台窗口中,单击下面的按钮(在右侧)

然后在下拉菜单中选择“Java Stack Trace Console”。

将堆栈跟踪粘贴到控制台。然后,它将提供到您的源代码和任何其他可用源代码的链接列表。

例如,如果我们有这样一个程序:

public class ExceptionTest {
    
    public static void main(String[] args) {
        int l = trimmedLength(null);
        System.out.println("Trimmed length = " + l);
    }
    
    private static int trimmedLength(String string) {
        return string.trim().length();
    }
}

你会得到这样的堆栈跟踪:

最近的方法调用(以及导致异常的方法调用)将位于堆栈的顶部,即顶部行(不包括错误消息文本)。在本例中,就是trimmedLength方法。从堆栈往下看,可以追溯到过去。第二行是调用第一行的方法,等等。

如果您使用的是开源软件,您可能需要下载源代码并将其附加到您的项目中进行检查。下载源jar,在你的项目中,打开references Libraries文件夹,找到开源模块的jar(带有类文件的那个),然后右键单击,选择Properties并附加源jar。

其他回答

简单来说,堆栈跟踪是抛出异常时应用程序正在执行的方法调用的列表。

简单的例子

通过问题中给出的示例,我们可以确定异常在应用程序中抛出的确切位置。让我们看一下堆栈跟踪:

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)为空。

带有异常链的示例

有时应用程序会捕获一个异常并将其作为另一个异常的原因重新抛出。这通常看起来像:

34   public void getBookIds(int id) {
35      try {
36         book.getId(id);    // this method it throws a NullPointerException on line 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: java.lang.NullPointerException <-- root cause
        at com.example.myproject.Book.getId(Book.java:22) <-- important line

同样,有了这个异常,我们想要查看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.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.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 com.example.myproject.OpenSessionInViewFilter.doFilter(OpenSessionInViewFilter.java:30)
    ... 27 more
Caused by: org.hibernate.exception.ConstraintViolationException: could not insert: [com.example.myproject.MyEntity]
    at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96)
    at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
    at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:64)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2329)
    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 org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
    at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:56)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
    at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:50)
    at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
    at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:705)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:693)
    at org.hibernate.impl.SessionImpl.save(SessionImpl.java:689)
    at sun.reflect.GeneratedMethodAccessor5.invoke(Unknown Source)
    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

在这个例子中,有更多。我们最关心的是寻找来自代码的方法,也就是com.example.myproject包中的任何方法。从上面的第二个例子中,我们首先要向下寻找根本原因,即:

Caused by: java.sql.SQLException

但是,在该方法下的所有方法调用都是库代码。因此,我们将移动到它上面的“Caused by”,在“Caused by”块中,寻找来自我们代码的第一个方法调用,它是:

at com.example.myproject.MyEntityService.save(MyEntityService.java:59)

像前面的例子一样,我们应该查看第59行上的MyEntityService.java,因为这是错误产生的地方(这个错误有点明显,因为SQLException声明了错误,但调试过程是我们所要做的)。

什么是Stacktrace?

stacktrace是一个非常有用的调试工具。它显示了抛出未捕获异常时(或手动生成堆栈跟踪的时间)的调用堆栈(即,到那时为止调用的函数的堆栈)。这是非常有用的,因为它不仅显示了错误发生的位置,而且还显示了程序是如何在代码的那个位置结束的。 这就引出了下一个问题:

什么是异常?

异常是运行时环境用来告诉您发生错误的东西。流行的例子是NullPointerException, IndexOutOfBoundsException或arithmeexception。这些都是当你试图做一些不可能的事情时造成的。例如,当你试图解引用一个null对象时,会抛出一个NullPointerException:

Object a = null;
a.toString();                 //this line throws a NullPointerException

Object[] b = new Object[5];
System.out.println(b[10]);    //this line throws an IndexOutOfBoundsException,
                              //because b is only 5 elements long
int ia = 5;
int ib = 0;
ia = ia/ib;                   //this line throws an  ArithmeticException with the 
                              //message "/ by 0", because you are trying to
                              //divide by 0, which is not possible.

我应该如何处理stacktrace /异常?

首先,找出导致异常的原因。尝试在谷歌上搜索异常的名称,以找出该异常的原因。大多数情况下,它是由错误的代码引起的。在上面给出的例子中,所有的异常都是由错误的代码引起的。所以对于NullPointerException的例子,你可以确保a在那个时候永远不为空。例如,你可以初始化一个或包含一个检查,就像这样:

if (a!=null) {
    a.toString();
}

这样,如果a==null,就不会执行出错的行。其他例子也一样。

Sometimes you can't make sure that you don't get an exception. For example, if you are using a network connection in your program, you cannot stop the computer from loosing it's internet connection (e.g. you can't stop the user from disconnecting the computer's network connection). In this case the network library will probably throw an exception. Now you should catch the exception and handle it. This means, in the example with the network connection, you should try to reopen the connection or notify the user or something like that. Also, whenever you use catch, always catch only the exception you want to catch, do not use broad catch statements like catch (Exception e) that would catch all exceptions. This is very important, because otherwise you might accidentally catch the wrong exception and react in the wrong way.

try {
    Socket x = new Socket("1.1.1.1", 6789);
    x.getInputStream().read()
} catch (IOException e) {
    System.err.println("Connection could not be established, please try again later!")
}

为什么我不应该使用catch(例外e)?

让我们用一个小例子来说明为什么不应该捕获所有异常:

int mult(Integer a,Integer b) {
    try {
        int result = a/b
        return result;
    } catch (Exception e) {
        System.err.println("Error: Division by zero!");
        return 0;
    }
}

这段代码试图做的是捕获由可能的除0引起的ArithmeticException。但它也捕获一个可能的NullPointerException,如果a或b为空则抛出该异常。这意味着,你可能会得到一个NullPointerException,但你会把它当作一个arithmeexception,可能会做错误的事情。在最好的情况下,你仍然错过了一个NullPointerException。这样的事情会使调试变得更加困难,所以不要这样做。

TLDR

找出异常的原因并修复它,这样它就根本不会抛出异常。 如果1。是不可能的,捕获特定的异常并处理它。 永远不要只添加一个try/catch,然后忽略异常!不要那样做! 永远不要使用catch (Exception e),总是捕获特定的异常。这样你就不用再头疼了。

补充一下罗伯提到的。在应用程序中设置断点可以逐步处理堆栈。这使开发人员能够使用调试器来查看方法在确切的哪个点上执行了未预料到的操作。

由于Rob已经使用NullPointerException (NPE)来说明一些常见的问题,我们可以通过以下方式帮助消除这个问题:

如果我们有一个接受参数的方法,比如:

在我们的代码中,我们希望计算firstName包含一个值,我们将这样做:if(firstName == null || firstName.equals("")) return;

以上可以防止我们使用firstName作为不安全的参数。因此,通过在处理之前进行空检查,我们可以帮助确保我们的代码能够正常运行。要展开一个利用对象的方法的例子,我们可以看这里:

If (dog == null || dog. If (dog == null || dog. If)firstName == null)返回;

以上是检查null的正确顺序,我们从基本对象开始,在本例中是dog,然后开始沿着可能性树向下走,以确保在处理之前所有内容都是有效的。如果顺序颠倒,可能会抛出一个NPE,我们的程序将崩溃。

To understand the name: A stack trace is a a list of Exceptions( or you can say a list of "Cause by"), from the most surface Exception(e.g. Service Layer Exception) to the deepest one (e.g. Database Exception). Just like the reason we call it 'stack' is because stack is First in Last out (FILO), the deepest exception was happened in the very beginning, then a chain of exception was generated a series of consequences, the surface Exception was the last one happened in time, but we see it in the first place.

关键一:这里需要理解的一个棘手而重要的事情是:最深层的原因可能不是“根本原因”,因为如果你写了一些“糟糕的代码”,它可能会导致一些比它的层更深的异常。例如,一个糟糕的sql查询可能会导致SQLServerException连接在底部重置,而不是syndax错误,这可能只是在堆栈的中间。

->在中间找到根本原因是你的工作。

关键2:另一个棘手但重要的事情是,在每个“Cause by”块中,第一行是最深的层,发生在该块的第一个位置。例如,

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)

Book.java:16是由Auther.java:25调用的,Bootstrap.java:14调用的,Book.java:16是根本原因。 这里附上一个按时间顺序对跟踪堆栈进行排序的图表。

Throwable家族还提供了一个堆栈跟踪特性——操作堆栈跟踪信息的可能性。

标准的行为:

package test.stack.trace;

public class SomeClass {

    public void methodA() {
        methodB();
    }

    public void methodB() {
        methodC();
    }

    public void methodC() {
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        new SomeClass().methodA();
    }
}

堆栈跟踪:

Exception in thread "main" java.lang.RuntimeException
    at test.stack.trace.SomeClass.methodC(SomeClass.java:18)
    at test.stack.trace.SomeClass.methodB(SomeClass.java:13)
    at test.stack.trace.SomeClass.methodA(SomeClass.java:9)
    at test.stack.trace.SomeClass.main(SomeClass.java:27)

操纵堆栈跟踪:

package test.stack.trace;

public class SomeClass {

    ...

    public void methodC() {
        RuntimeException e = new RuntimeException();
        e.setStackTrace(new StackTraceElement[]{
                new StackTraceElement("OtherClass", "methodX", "String.java", 99),
                new StackTraceElement("OtherClass", "methodY", "String.java", 55)
        });
        throw e;
    }

    public static void main(String[] args) {
        new SomeClass().methodA();
    }
}

堆栈跟踪:

Exception in thread "main" java.lang.RuntimeException
    at OtherClass.methodX(String.java:99)
    at OtherClass.methodY(String.java:55)