重学Java异常体系

Java异常类的层次结构

Throwable是所有异常类的基类。
Throwable包含了其线程创建时线程执行堆栈的快照,它提供了 printStackTrace() 等接口用于获取堆栈跟踪数据等信息。

Throwable 分为两种:Error和Exception。
1.Error:Error是系统级别的错误,无法通过程序处理;此类错误一般表示代码运行时 JVM 出现问题,例如OutOfMemoryError、StackOverflowError、VirtualMachineError等。

2.Exception是应用程序级别的异常,可以通过程序处理;通常会分为Checked Exception和Unchecked Exception。

  • Checked Exception,也称为受检异常,在编译时就需要进行处理,否则会报编译错误,例如IOException、SQLException等。
  • Unchecked Exception,也称为非受检异常,在运行时才会抛出,不需要进行强制处理,例如NullPointerException、ArrayIndexOutOfBoundsException等。

受检异常 vs 非受检异常

受检异常
  • 本质是应用程序执行过程中可能遇到的异常情形,必须由当前调用方处理;即调用方必须捕获并处理被调方法签名上声明的异常;
  • 这类异常,在一定程度上它的发生是可以预计的(例如从配置文件读取配置项,在程序运行之前就可以预计到文件可能不存在),而一旦发生异常状况,就必须采取某种方式进行处理。受检异常在编译期必须显式地被处理,否则编译器将无法通过代码。

在java.lang.Exception类注释上,说明了Exception及其子类(排除RuntimeException及其子类),都是checked exceptions受检异常。

/**
 * The class Exception and its subclasses are a form of
 * {@code Throwable} that indicates conditions that a reasonable
 * application might want to catch.
 *
 * The class Exception and any subclasses that are not also
 * subclasses of {@link RuntimeException} are checked
 * exceptions  Checked exceptions need to be declared in a
 * method or constructor's {@code throws} clause if they can be thrown
 * by the execution of the method or constructor and propagate outside
 * the method or constructor boundary.
 */
public class Exception extends Throwable {...}
非受检异常
  • 在一定程度上它的发生是不可预计的,只有实际运行到那时才可能发生;比如访问数组越界,程序运行之前是无法预计的。JDK将这类异常命名为RuntimeException运行时异常也恰如其分。
  • 这类异常的发生通常是由于程序 bug 所致,应该尽量通过预先检查进行规避;比如在面对可能抛NullPointerException的地方时主动判断是不是null 并处理、循环处理时要检查下标边界防止IndexOutOfBounds。

在java.lang.RuntimeException类注释上,说明了RuntimeException及其子类,都是Unchecked Exception非受检异常。

/**
 * RuntimeException is the superclass of those
 * exceptions that can be thrown during the normal operation of the
 * Java Virtual Machine.
 *
 * RuntimeException and its subclasses are unchecked exceptions.  
 *  Unchecked exceptions do not need to be
 * declared in a method or constructor's {@code throws} clause if they
 * can be thrown by the execution of the method or constructor and
 * propagate outside the method or constructor boundary.
 *
 * @author  Frank Yellin
 * @jls 11.2 Compile-Time Checking of Exceptions
 * @since   JDK1.0
 */
public class RuntimeException extends Exception {...}

Java异常处理机制

Java 的异常处理,主要是通过 5 个关键字来实现:try、catch、throw、throws 和 finally。
通过捕获和处理异常,能够在程序出现异常情况时提供更好的容错机制,保证程序的稳定性和可靠性。

throws与throw

  • throws出现在方法签名的声明中,表示该方法可能会抛出的异常,然后交给上层调用它的方法程序处理,允许throws后面跟着多个异常类型;一般用在程序运行之前就预计可能发生的异常场景上,让调用方显示处理。
  • throw只会出现在方法体中,当方法在执行过程中遇到异常情况时,将异常信息封装为异常对象,然后throw出去。

throw 关键字的作用是抛出一个异常,但是它的作用不仅仅局限于抛出异常。在一些情况下,throw 关键字还可以实现异常类型的转换。这是因为 Java 中的异常继承关系,子类异常对象可以转换为父类异常对象,而 throw 关键字可以将子类异常对象强制转换为父类异常对象。

public void doSomething() throws Exception {
    try {
        // do something
    } catch (ChildException e) { // 子类异常
        throw (Exception)e; // 异常类型转换为父类异常
    }
}

throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常对象。

关于finally特殊问题和Java异常处理的最佳实践,后面用单独的文章讨论。

覆盖父类方法时的异常声明

从父类继承,并且子类重写/覆盖的方法签名,如何声明异常?
子类重写父类方法的时候,如何确定异常抛出声明的类型。主要有4个原则:

1.子类重写父类方法时,要抛出与父类一致的异常,或者不抛出异常

2.子类重写父类方法时,子类抛出的异常不能超过父类的受检异常类型范围
即如果父类的方法声明了受检异常T,则子类在重写该方法的时候声明的异常不能是 T的父类;只能是T及T的子类。

3.子类重写的方法可以抛出任意非受检异常

4.子类在重写父类的具有异常声明的方法的同时,又去实现了具有相同方法名称的接口且该接口中的方法也具有异常声明,则子类中的重写的方法,要么不抛出异常,要么抛出父类中方法声明异常与接口中方法声明的异常的交集。

自定义异常

在 Java 中,通常情况下已有的内置异常类能够满足应用的基础使用需求。但有些情况下,需要自定义异常来满足特别的需求。
最常见的就是用精确的命名描述异常

  • 例如,某个特定case的异常,让它见名知意。当程序需要对异常情况进行更加具体和准确的描述时,可以自定义异常类。

如何自定义异常就不多描述了,这里着重说一点关于自定义异常时需要注意的地方。

重写fillInStackTrace方法

Java 异常在程序运行时发生,会导致程序的执行流程被中断并进行一系列的异常处理操作,这会对程序的性能产生一定的影响。其中性能开销最大的是填充堆栈。
默认情况下,是由Throwable的fillInStackTrace方法完成,它是一个native方法;作用是获取当前线程的堆栈信息,填充到跟踪元素中。

public synchronized Throwable fillInStackTrace() {
    if (stackTrace != null || backtrace != null ) {// 如果没填充过……
        fillInStackTrace(0);                // 获取当前线程的堆栈跟踪信息
        stackTrace = UNASSIGNED_STACK;      // 填充堆栈跟踪信息
    }
    return this;// 如果已经填充过,直接返回当前异常对象
}

一般来说自定义的业务异常如果确实不需要stacktrace的话,可以覆写该方法,返回this,提高性能。

@Override
public synchronized Throwable fillInStackTrace() {
    // return super.fillInStackTrace();
    return this;
}

当前还有其他实现方式:

  • jvm参数控制栈深度,物理屏蔽;
  • logback日志框架控制堆栈的输出深度,逻辑屏蔽;
  • jvm本身对某些异常也做了优化,jvm有个参数OmitStackTraceInFastThrow(省略异常快速抛出), 如果检测到在代码里某个位置连续多次抛出同一类型异常的话,C2即时编译器会决定用Fast Throw方式来抛出异常,而异常Trace即详细的异常栈信息会被清空。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容