前言

Java异常是在Java应用中的警报器,在出现异常的情况下,可以帮助我们程序猿们快速定位问题的类型以及位置。但是一般在我们的项目中,由于经验阅历等多方面的原因,依然有若干的童鞋在代码中没有正确的使用异常打印方法,导致在项目的后台日志中,没有收到日志或者日志信息不完整等情况的发生,这些都给项目埋下了若干隐患。本文将深入分析在异常日志打印过程中的若干情况,并给出若干的使用建议。

1. Java异常Exception的结构分析

我们通常所说的Exception主要是继承于Throwable而来,可以参见如下的结构图示:

主要的Throwable分为异常和错误两种,然后异常Exception和错误Error做为基类,分别被具体个性化以及衍生出NullPointerException、EOFException等等异常信息类。

基于Java中的源代码来分析,Error和Exception仅仅是继承了Throwable,做了构造函数的拓展,没有进行额外方法的延展;Exception输出的主要核心方法都是定义在Throwable中的,感兴趣的童鞋可以尝试阅读JDK的源代码。

下面将介绍一下关键的几个异常类方法:

1、getMessage(): String

输出异常的描述信息

2、getLocalizedMessage()

输出本地化的描述信息,一般此方法可被子类所覆盖,缺省实现与getMessage()输出信息一致

3、printStackTrace()

将异常栈打印到输出流中,此为一类方法,默认打印到console控制台,也可以显式指定输出流。

4、fillInStackTrace()

将当前的异常栈保存到一个Throwable中,返回这个Throwable。大部分情况下,用在保留异常栈嵌套调用的情况,尝试保留完整的异常栈,无需使用该方法。

2. Error vs Exception

Error在Java体系中定义为不可控制的问题,往往用来描述系统错误或者底层的问题,比如虚拟机错误,例如内存空间不足,方法调用栈溢等。我们以上图中列举出的内存溢出错误(StackOverflowError)为例,它是在JVM层面发生的错误,已经游离于java应用层之外;在应用程序层面是无法进行捕获,且无法从错误中恢复的。一般一旦发了类似问题,一般都是直接宕机,应用停止正常的工作,需要重新启动或者修复问题之后,方可重新正常工作。

Exception一般发生在应用层,即在由项目中的Java代码层面引发的问题,且可以尝试进行捕获,此类问题不会影响到应用程序的正常工作的,即不会导致宕机现象的发生。我们在工作或者代码中常见的都是Exception衍生出来的各类异常。

这里需要强调说明一下,JVM是Java语言的运行环境和平台,但是并不是Java语言体系的一个部分;在JVM平台上,还可以运行Groovy, JPython, JRuby, Closure,Scala等等遵守Java语言规范(JavaLanguage Specification)的编程语言,故我们可以将Error理解为脱离Java应用之外的问题。

3. Exception中的运行时异常(RuntimeException)和受控异常(checked exception)

运行时异常(RuntimeException)是指在运行之时发生的异常,无需显式地进行捕获;如果程序中发生类似的异常,JVM会直接抛出此类异常,并打出响应的异常栈信息。此类异常也通常被称为unchecked exception, 未受控异常。

受控异常(checked Exception)是我们最常见的异常种类,在代码中使用的异常基本上都是此类异常,此类异常会在代码编译阶段由Java编译器进行语法检查,如果未显式进行异常捕获,则会报出相应的编译异常信息。

4. 如何在代码中正确打印异常信息

下面我们将通过一系列的例子来说明上述几个Exception中方法的使用技巧。

Case 1: getMessage()/getLocalizedMessage()

?

1

2

3

4

5

6

7

8

9

public

void

testCase1() {

System.out.println(

"We are going to do something interesting...."

);

try

{

throw

new

NullPointerException(

"I am an exception in the code."

);

}

catch

(Exception e) {

System.out.println(

"We got unexpected:"

+ e.getMessage());

System.out.println(

"We got unexpected:"

+ e.getLocalizedMessage());

}

}

输出结果:

?

1

2

3

We are going to

do

testing interesting....

We got unexpected in getMessage==> I am an exception in the code.

We got unexpected in getLocalizedMessage==> I am an exception in the code.

基于结果来分析, 上述两个方法只是将异常对象中的Message打印出来,这些信息对于我们追踪问题和调试帮助有限。

Case 2:e.printStackTrace()

?

1

2

3

4

5

6

7

8

public

void

testCase2() {

System.out.println(

"We are going to do something interesting...."

);

try

{

throw

new

Exception(

"I am an exception in the code."

);

}

catch

(Exception e) {

e.printStackTrace();

}

}

运行结果:

运行结果图

printStackTrace()可以打印出整个异常栈,但是异常栈信息将输出到默认的输出流中,绝大多数情况下是系统的控制台,而在实际项目中,都是需要将异常栈输出到日志文件的,如果不显式指定,则会丢失异常信息,在日志文件中无从追查。

Case 3: 基于log4j/slf4j等输出到日志文件

在实际项目中,一般会使用log4j/log4j2/JDK logging/slf4j/logback等日志系统来存放日志,那如何来讲日志的异常栈存入日志文件呢,我们来看示例。

?

1

2

3

4

5

6

7

8

public

void

testCase3() {

System.out.println(

"We are going to do something interesting...."

);

try

{

throw

new

NullPointerException(

"abcedfeg"

);

}

catch

(Exception e) {

logger.info(

"Unexpected error in "

, e);

}

}

我们需要到日志文件中,找到相应的异常信息,异常信息如下:

?

1

2

3

4

12

:

24

:

45.387

[main] INFO org.demo.TestException - Unexpected error in

java.lang.NullPointerException: abcedfeg

at org.demo.TestException.testCase3(TestException.java:

39

)

at org.demo.TestException.main(TestException.java:

12

)

我们可以发现,整个异常栈信息由两个部分组成:

>> 异常中的message, 类似getMessage()输出的信息,

>> 使用logger.info之类的方法,将异常信息写入到日志流中.

以下为log4j的声明,这里以slf4j为例来示例:

?

1

private

static

final

Logger logger = LoggerFactory.getLogger(TestException.

class

);

Case 4: 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

FillInExceptionTest {

public

static

void

main(String[] args) {

FillInExceptionTest fit =

new

FillInExceptionTest();

try

{

fit.outerMehtod();

}

catch

(Exception e) {

System.out.println(

"\n==========I am the one evil separation line=============="

);

e.printStackTrace();

}

}

public

void

innerMethod()

throws

Exception {

throw

new

Exception(

"I got exception in an inner method."

);

}

public

void

outerMehtod()

throws

Exception {

try

{

innerMethod();

//invoke inner method.

}

catch

(Exception e) {

e.printStackTrace();

throw

(Exception)e.fillInStackTrace();

}

}

}

运行结果:

基于上述的运行结果可知,fillInStackTrace()只提取了当下的异常栈信息,而非完整的异常栈信息,这个就是此方法带给我们的特殊之处。

如果我们需要在最外层将完整的异常栈打印出来,该如何做呢? 将下述的语句:

?

1

throw

(Exception)e.fillInStackTrace();

替换为:

?

1

throw

e;

重新运行程序,我们就可以在最外层得到完整的异常栈信息了。

5. 总结

在本文中,我们介绍了异常类的继承体系,不同类型的异常区别与使用场景;并将基于代码示例展示了如何使用Exception的若干方法,利用这些方法来保留尽可能多的日志信息,方便我们后续针对日志中的异常信息,追查和解决问题。

getStackTrace和printStackTrace的区别

在JAVA中收到程序报错,将堆栈信息打印出来是一个好习惯,但是在catch到exception之后,发现有两个方法都和堆栈信息有关,一个是getStackTrace,一个是printStackTrace,那么他们的区别是什么?

我们从源码的角度来分析:

(1)getStackTrace()

[java]

view plain

copy

public StackTraceElement[] getStackTrace() {        return getOurStackTrace().clone();    }      private synchronized StackTraceElement[] getOurStackTrace() {        // Initialize stack trace field with information from        // backtrace if this is the first call to this method        if (stackTrace == UNASSIGNED_STACK ||            (stackTrace == null && backtrace != null) /* Out of protocol state */) {            int depth = getStackTraceDepth();            stackTrace = new StackTraceElement[depth];            for (int i=0; i < depth; i++)                stackTrace[i] = getStackTraceElement(i);        } else if (stackTrace == null) {            return UNASSIGNED_STACK;        }        return stackTrace;    }

(2)printStackTrace()

[java]

view plain

copy

public void printStackTrace() {         printStackTrace(System.err);     }       /**     * Prints this throwable and its backtrace to the specified print stream.     *     * @param s {@code PrintStream} to use for output     */     public void printStackTrace(PrintStream s) {         printStackTrace(new WrappedPrintStream(s));     }       private void printStackTrace(PrintStreamOrWriter s) {         // Guard against malicious overrides of Throwable.equals by         // using a Set with identity equality semantics.         Set dejaVu =             Collections.newSetFromMap(new IdentityHashMap());         dejaVu.add(this);           synchronized (s.lock()) {             // Print our stack trace             s.println(this);             StackTraceElement[] trace = getOurStackTrace();             for (StackTraceElement traceElement : trace)                 s.println("\tat " + traceElement);               // Print suppressed exceptions, if any             for (Throwable se : getSuppressed())                 se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);               // Print cause, if any             Throwable ourCause = getCause();             if (ourCause != null)                 ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);         }     }

getStackTrace()返回的是通过getOurStackTrace方法获取的StackTraceElement[]数组,而这个StackTraceElement是ERROR的每一个cause by的信息。

printStackTrace()返回的是一个void值,但是可以看到其方法内部将当前传入打印流锁住,然后同样通过getOurStackTrace方法获取的StackTraceElement[]数组,只不过printStackTrace()方法直接打印出来了。而getStackTrace()则是得到数组,使用者可以根据自己的需求去得到打印信息,相比printStackTrace()会更细一些。

这里指的一提的是slf4j的log方法,如果放入Exception对象,则会自动打印出其堆栈信息,不必要再专门去转流写入日志。

JAVA产生异常并打印,Java中异常打印输出的常见方法整理相关推荐

  1. java e.getmessage() null,浅谈Java异常的Exception e中的egetMessage()和toString()方法的区别...

    Exception e中e的getMessage()和toString()方法的区别: 示例代码1: public class TestInfo { private static String str ...

  2. python输出列表元素_在Python中分别打印列表中的每一个元素方法

    在Python中分别打印列表中的每一个元素方法 更新时间:2018年11月07日 15:12:03 作者:wintersshi 今天小编就为大家分享一篇在Python中分别打印列表中的每一个元素方法, ...

  3. js中修改this的指向方法整理

    JavaScript(简称"JS") 是一种具有函数优先的轻量级,解释型或即时编译型的编程语言.虽然它是作为开发Web页面的脚本语言而出名,但是它也被用到了很多非浏览器环境中,Ja ...

  4. java中异常的定义_java中异常的理解

    Trowable是所有异常的超类,对应的底下有两个子类,Error和Exception,java中的异常其实总体上来说有三种类型: 一.检查性异常,程序在编译的时候出现的异常,例如打开一个文件,此时程 ...

  5. java 变参 使用数组调用_java中的可变参数使用方法

    java中的可变参数使用方法 可变参数时Java 1.5新增的方法,可变参数方法接收0个或者多个指定类型的参数,可变参数机制通过先创建一个数组,数组的大小为在调用位置所传递的参数数量,然后将参数值传到 ...

  6. java xml二进制流传输_XML中二进制数据的处理方法

    XML中二进制数据的处理方法 在xml中,所有的数据都是以文本的形式来显示,但是二进制数据不能直接以文本格式来表示,那xml又是怎么处理二进制数据的呢?下面就来探讨一下. 为了简单和通用性,xml被设 ...

  7. Python异常重试解决方案 Python中异常重试的解决方案详解

    想了解Python中异常重试的解决方案详解的相关内容吗,标点符在本文为您仔细讲解Python异常重试解决方案的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:python,重试,python ...

  8. java屏蔽虚拟按键代码_Android 中隐藏虚拟按键的方法实例代码

    下面通过一段代码给大家讲解android 隐藏虚拟按键的方法,废话不多说了,大家多多看看代码和注释吧,具体代码如下所示: /** * 隐藏虚拟按键,并且全屏 */ protected void hid ...

  9. java log4j 路径配置_log4j配置文件中路径配置一般有三种方法:

    log4j配置文件中路径配置一般有三种方法: (1)绝对路径法:直接配置为系统觉得路径: (2)相对路径法: log4j.appender.logfile.File=../logs/app.log,将 ...

最新文章

  1. 机器学习数据预处理之离群值/异常值:箱图法(boxplot method)
  2. Xcode8 注释快捷键无效, 解决方案
  3. oracle忘记密码
  4. 【HDU - 1540】 Tunnel Warfare (线段树进阶操作 区间合并+ 单点更新+ 最长覆盖区间查询 )
  5. [转载] JAVA语言程序设计(基础篇)第十版课后题答案(第一章)
  6. 根本没人买!又一品牌宣布不再做手机了...
  7. vscode 支持ansi_vscode terminal美化
  8. BigBrother服务器端管理脚本_Bash
  9. 2017-03-10Git撤销修改
  10. 电脑怎么进入linux系统,Linux操作系统进入家用电脑成为发展新前景
  11. 【面经】深度学习算法岗(京东)
  12. python2.7 UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5
  13. unity怎么制作云飘动_现实的动态云系统特效脚本Unity3D素材资源
  14. 关注程序员健康,使用中医十二经络来解释一个神奇的梦境!
  15. perl统计日志文件ip及数量
  16. Istio中的流量管理
  17. 太阳能监控摄像头电不够用
  18. Homework7_ch5 数据的共享与保护(2)——friend
  19. iOS开发 字体的几种设置方法
  20. Nginx部署静态资源

热门文章

  1. 分享18个程序员最喜欢逛的国外论坛
  2. 思考:高并发下的抽奖优化
  3. Git之多人协同开发
  4. 小羊驼和你一起学习cocos2d-x与lua之四(泛型for学习)
  5. 【STM32F407的DSP教程】第25章 DSP变换运算-快速傅里叶变换原理(FFT)
  6. Web前端day03-CSSC选择器
  7. 光伏数据采集分析系统
  8. Netapp大概简介(一)
  9. idea 无法下载源码问题解决
  10. 【Android】41.最简单的方法设置沉浸状态栏