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

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)

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


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


当前回答

为了补充其他示例,有一些内部(嵌套)类以$符号出现。例如:

public class Test {

    private static void privateMethod() {
        throw new RuntimeException();
    }

    public static void main(String[] args) throws Exception {
        Runnable runnable = new Runnable() {
            @Override public void run() {
                privateMethod();
            }
        };
        runnable.run();
    }
}

将导致以下堆栈跟踪:

Exception in thread "main" java.lang.RuntimeException
        at Test.privateMethod(Test.java:4)
        at Test.access$000(Test.java:1)
        at Test$1.run(Test.java:10)
        at Test.main(Test.java:13)

其他回答

什么是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),总是捕获特定的异常。这样你就不用再头疼了。

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

如果您获得堆栈跟踪,并希望跟踪异常的原因,那么理解它的一个良好起点是使用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。

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)

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

由于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,我们的程序将崩溃。