本文转自wawlian

捕获到异常时,往往需要进行一些处理。比较简单直接的方式就是打印异常栈轨迹Stack Trace。说起栈轨迹,可能很多人和我一样,第一反应就是printStackTrace()方法。其实除了这个方法,还有一些别的内容也是和栈轨迹有关的。

1.printStackTrace()

首先需要明确,这个方法并不是来自于Exception类。Exception类本身除了定义了几个构造器之外,所有的方法都是从其父类继承过来的。而和异常相关的方法都是从java.lang.Throwable类继承过来的。而printStackTrace()就是其中一个。

这个方法会将Throwable对象的栈轨迹信息打印到标准错误输出流上。输出的大体样子如下:

1
2
3
4
java.lang.NullPointerException
         at MyClass.mash(MyClass.java:9)
         at MyClass.crunch(MyClass.java:6)
         at MyClass.main(MyClass.java:3)

输出的第一行是toString()方法的输出,后面几行的内容都是之前通过fillInStackTrace()方法保存的内容。关于这个方法,我们后面会讲。

下面看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestPrintStackTrace {
    public static void f() throws Exception{
        throw new Exception("出问题啦!");
    }
    public static void g() throws Exception{
        f();
    }
    public static void main(String[] args) {
        try {
            g();
        }catch(Exception e) {
            e.printStackTrace();
        }
    }
}

这个例子的输出如下:

1
2
3
4
java.lang.Exception: 出问题啦!
    at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
    at TestPrintStackTrace.g(TestPrintStackTrace.java:6)
    at TestPrintStackTrace.main(TestPrintStackTrace.java:10)

在这个例子中,在方法f()中抛出异常,方法g()中调用方法f(),在main方法中捕获异常,并且打印栈轨迹信息。因此,输出依次展示了f—>g—>main的过程。

2.getStackTrace()方法

这个方法提供了对printStackTrace()方法所打印信息的编程访问。它会返回一个栈轨迹元素的数组。以上面的输出为例,输出的第2-4行每一行的内容对应一个栈轨迹元素。将这些栈轨迹元素保存在一个数组中。每个元素对应栈的一个栈帧。数组的第一个元素保存的是栈顶元素,也就是上面的f。最后一个元素保存的栈底元素。

下面是一个使用getStackTrace()访问这些轨迹栈元素并打印输出的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TestPrintStackTrace {
    public static void f() throws Exception{
        throw new Exception("出问题啦!");
    }
    public static void g() throws Exception{
        f();
    }
    public static void main(String[] args) {
        try {
            g();
        }catch(Exception e) {
            e.printStackTrace();
            System.out.println("------------------------------");
            for(StackTraceElement elem : e.getStackTrace()) {
                System.out.println(elem);
            }
        }
    }
}

这样的输出和printStackTrace()的输出基本上是一样的,如下:

1
2
3
4
5
6
7
java.lang.Exception: 出问题啦!
    at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
    at TestPrintStackTrace.g(TestPrintStackTrace.java:6)
    at TestPrintStackTrace.main(TestPrintStackTrace.java:10)
TestPrintStackTrace.f(TestPrintStackTrace.java:3)
TestPrintStackTrace.g(TestPrintStackTrace.java:6)
TestPrintStackTrace.main(TestPrintStackTrace.java:10)

3.fillInStackTrace()

我们在前面也提到了这个方法。要说清楚这个方法,首先要讲一下捕获异常之后重新抛出的问题。在catch代码块中捕获到异常,打印栈轨迹,又重新throw出去。在上一级的方法调用中,再捕获这个异常并且打印出栈轨迹信息。这两个栈轨迹信息会一样吗?我们看一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestPrintStackTrace {
    public static void f() throws Exception{
        throw new Exception("出问题啦!");
    }
    public static void g() throws Exception{
        try {
            f();
        }catch(Exception e) {
            e.printStackTrace();
            throw e;
        }
         
    }
    public static void main(String[] args) {
        try {
            g();
        }catch(Exception e) {
            e.printStackTrace();
        }
    }
}

在main方法中捕获的异常,是在g()方法中抛出的,按理说这两个打印栈轨迹的信息应该不同,第二次打印的信息应该没有关于f的信息。但是事实上,两次打印栈轨迹信息是一样的。输出结果如下:

1
2
3
4
5
6
7
8
java.lang.Exception: 出问题啦!
    at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
    at TestPrintStackTrace.g(TestPrintStackTrace.java:7)
    at TestPrintStackTrace.main(TestPrintStackTrace.java:16)
java.lang.Exception: 出问题啦!
    at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
    at TestPrintStackTrace.g(TestPrintStackTrace.java:7)
    at TestPrintStackTrace.main(TestPrintStackTrace.java:16)

也就是说,捕获到异常又立即抛出,在上级方法调用中再次捕获这个异常,打印的栈轨迹信息是一样的。原因在于没有将当前线程当前状态下的轨迹栈的状态保存进Throwabe中。现在我们引入fillInStackTrace()方法。这个方法刚好做的就是这样的保存工作。我们看一下这个方法的原型:

1
public Throwable fillInStackTrace()

这个方法是有返回值的。返回的是保存了当前栈轨迹信息的Throwable对象。我们看看使用fillInStackTrace()方法处理后,打印的栈轨迹信息有什么不同,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TestPrintStackTrace {
    public static void f() throws Exception{
        throw new Exception("出问题啦!");
    }
    public static void g() throws Exception{
        try {
            f();
        }catch(Exception e) {
            e.printStackTrace();
            //不要忘了强制类型转换
            throw (Exception)e.fillInStackTrace();
        }
         
    }
    public static void main(String[] args) {
        try {
            g();
        }catch(Exception e) {
            e.printStackTrace();
        }
    }
}

输出如下:

1
2
3
4
5
6
7
java.lang.Exception: 出问题啦!
    at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
    at TestPrintStackTrace.g(TestPrintStackTrace.java:7)
    at TestPrintStackTrace.main(TestPrintStackTrace.java:17)
java.lang.Exception: 出问题啦!
    at TestPrintStackTrace.g(TestPrintStackTrace.java:11)
    at TestPrintStackTrace.main(TestPrintStackTrace.java:17)

我们看到,在main方法中打印栈轨迹已经没有了f相关的信息了。

  4.fillInStackTrace()续

fillInStackTrace这个方法还是很有特点的,竟然能够一直追踪调用自己的那些方法,甚至java的行数,本想仔细看看源代码的,没想到竟然是native的方法,而且api文档中只有一句话,实在是让人很难知道它的真正用法。do了一下research之后,有一些心得。

package 捕捉异常;

public class FillInStackTraceDemo {     

         Throwable th =  new Throwable();

         public FillInStackTraceDemo() {
System.out.println("in constructor");
} public void a()
{
c();
} public void b()
{
System.out.println("in b");
th.fillInStackTrace();
th.printStackTrace(System.out);
System.out.println("in b");
} public void c()
{
b();
th.fillInStackTrace();
System.out.println("in c");
th.printStackTrace(System.out);
System.out.println("in c");
} public static void main(String [] args)
{
FillInStackTraceDemo t3 = new FillInStackTraceDemo();
t3.a();
}
} /*in constructor
in b
java.lang.Throwable
at 捕捉异常.FillInStackTraceDemo.b(FillInStackTraceDemo.java:19)
at 捕捉异常.FillInStackTraceDemo.c(FillInStackTraceDemo.java:26)
at 捕捉异常.FillInStackTraceDemo.a(FillInStackTraceDemo.java:13)
at 捕捉异常.FillInStackTraceDemo.main(FillInStackTraceDemo.java:37)
in b
in c
java.lang.Throwable
at 捕捉异常.FillInStackTraceDemo.c(FillInStackTraceDemo.java:27)
at 捕捉异常.FillInStackTraceDemo.a(FillInStackTraceDemo.java:13)
at 捕捉异常.FillInStackTraceDemo.main(FillInStackTraceDemo.java:37)
in c*/

   fillInStackTrace每次执行的时候,会清空原来的栈内的trace信息。然后在当前的调用位置处重新建立trace信息, 所以在方法b()中printStackTrace的执行结果跟c()中的是不一样的。

以上就是关于Java栈轨迹的一些我之前没有掌握的内容,记下来备忘。

基础知识《十四》Java异常的栈轨迹fillInStackTrace和printStackTrace的用法的更多相关文章

  1. Java异常的栈轨迹fillInStackTrace和printStackTrace的用法

    本文转自wawlian 捕获到异常时,往往需要进行一些处理.比较简单直接的方式就是打印异常栈轨迹Stack Trace.说起栈轨迹,可能很多人和我一样,第一反应就是printStackTrace()方 ...

  2. 正则表达式、Calendar类、SimpleDateFormat类、Date类、BigDecimal类、BigInteger类、System类、Random类、Math类(Java基础知识十四)

    1.正则表达式的概述和简单使用 * A:正则表达式(一个字符串,是规则)     * 是指一个用来描述或者匹配一系列符合某个语法规则的字符串的单个字符串.其实就是一种规则.有自己特殊的应用. * B: ...

  3. Android学习之基础知识十四 — Android特色开发之基于位置的服务

    一.基于位置的服务简介 LBS:基于位置的服务.随着移动互联网的兴起,这个技术在最近的几年里十分火爆.其实它本身并不是什么时髦的技术,主要的工作原理就是利用无线电通讯网络或GPS等定位方式来确定出移动 ...

  4. oracle 基础知识(十四)----索引扫描

    (1)索引唯一扫描(index unique scan) 通过唯一索引查找一个数值经常返回单个ROWID.如果该唯一索引有多个列组成(即组合索引),则至少要有组合索引的引导列参与到该查询中,如创建一个 ...

  5. ASP.NET Core 2.2 基础知识(十四) WebAPI Action返回类型(未完待续)

    要啥自行车,直接看手表 //返回基元类型 public string Get() { return "hello world"; } //返回复杂类型 public Person ...

  6. IM开发基础知识补课(四):正确理解HTTP短连接中的Cookie、Session和Token

    本文引用了简书作者“骑小猪看流星”技术文章“Cookie.Session.Token那点事儿”的部分内容,感谢原作者. 1.前言 众所周之,IM是个典型的快速数据流交换系统,当今主流IM系统(尤其移动 ...

  7. Bootstrap<基础二十四> 缩略图

    Bootstrap 缩略图.大多数站点都需要在网格中布局图像.视频.文本等.Bootstrap 通过缩略图为此提供了一种简便的方式.使用 Bootstrap 创建缩略图的步骤如下: 在图像周围添加带有 ...

  8. Java15-java语法基础(十四)抽象类

    Java15-java语法基础(十四)抽象类 一.抽象类的作用 三个类都有"执行任务"的方法,分别在这三个类中进行定义,因此需要重复编写代码,降低了程序开发效率,且增加了程序出现错 ...

  9. 基础知识《十》java 异常捕捉 ( try catch finally ) 你真的掌握了吗?

    本文转载自  java 异常捕捉 ( try catch finally ) 你真的掌握了吗? 前言:java 中的异常处理机制你真的理解了吗?掌握了吗?catch 体里遇到 return 是怎么处理 ...

随机推荐

  1. 10-hibernate单表操作-组件属性

    组件属性: 实体类中某个属性属于用户自定义的类的对象,比如在实体类中某个属性是自定义类的对象: 这个Address是一个用户自定义类. 该自定义类Address定义如下: //地址类 public c ...

  2. 【微信小程序】转载:微信小程序实战篇-下拉刷新与加载更多

    下拉刷新 实现下拉刷新目前能想到的有两种方式 1. 调用系统的API,系统有提供下拉刷新的API接口 当然,你可以直接在全局变量app.json的window里面配置上面这个属性,这样整个项目都允许下 ...

  3. (四)Lucene——搜索和相关度排序

    1. 搜索 1.1 创建查询对象的方式 通过Query子类来创建查询对象 Query子类常用的有:TermQuery.NumericRangeQuery.BooleanQuery 特点:不能输入luc ...

  4. Linux-查看C语言手册及man的特殊用法

    man命令可以查看c语言库函数的函数原型, 比如 $ man malloc 如果显示 "No manual entry for malloc", 则需要安装 "man-p ...

  5. redis windows 下安装及使用

    1.下载redis https://github.com/MSOpenTech/redis 2.解压下载的文档,比如D:\devSoft\redis-2.8.19 redis-benchmark.ex ...

  6. Unity中差乘判断目标是否在左边或右边

    使用差乘判断左右一般是比较差乘的y,小于0是左,大于0是右.特殊情况可以用其他分量来比较 默认情况: var cross = Vector3.Cross(lhsObject.transform.pos ...

  7. Atitit。Js调用后台语言 java c#  php swing android  swt的方法大总结

    Atitit.Js调用后台语言 java c#  php swing android  swt的方法大总结 1. Js调用后台语言有三种方法1 2. Swt  BrowserFunction 绑定方法 ...

  8. atitit. 分销系统规划p8k

    atitit. 分销系统规划p8k 1. 商户平台管理 overview2 1.1. 分销业务管理2 1.2. 文案管理2 1.3. 订单管理3 1.4. 统计报表3 1.5. 财务结算3 1.6.  ...

  9. [na]台式机装原版Win2008R2

    坑了老半天,总结出几点 1,系统os下载: http://msdn.itellyou.cn/ 注:其他地方下载的,装后发现不是起不来就是驱动装不了. 2,u盘里放个压缩软件: 好呀压缩   和  浏览 ...

  10. C# 执行多条SQL更新语句,实现数据库事务

    class Program { class Result<T> { public T data; public string Message; public bool Success; p ...