【Java核心技术卷】谈谈Java中的异常

在我们的代码生涯中,异常情况如影相随。既然避免不了,为了程序的健壮性,还是很有必要了解一下Java中的异常机制。

用一张图展开这次的讨论内容:
【Java核心技术卷】谈谈Java中的异常
从图中可以看到:

Throwable就像一道分水岭,把Exception和Error分成了两条河道,下面让我们好好探讨一下。


Throwable类是Java异常类型的顶层父类,只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型


Exception 和 Error 都继承了 Throwable 类,Exception 和 Error 能够分别体现了 Java 平台设计者对不同异常情况的分类:

  • Exception 是程序正常运行中,可以预料的意外情况,可能并且应该被捕获,进行相应处理。
  • Error 是指在正常情况下,不大可能出现的情况,绝大部分的 Error 都会导致程序(比如 JVM自身)处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如 OutOfMemoryError 之类,都是 Error 的子类。

Exception 又分为可检查(checked)异常不检查(unchecked)异常

  • 可检查异常在源代码里必须显式地进行捕获处理,这是编译期检查的一部分。
  • 不检查异常就是所谓的运行时异常,类似 NullPointerException、ArrayIndexOutOfBoundsException 之类,通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求。

Error 是指在正常情况下,不大可能出现的情况,绝大部分的 Error 都会导致程序(比如 JVM自身)处于非正常的、不可恢复状态。既然是非正常情况,所以不便于也不需要捕获,常见的比如 OutOfMemoryError 之类,都是 Error 的子类。

知道它们三个的情况,我们就可以继续往下走了,这里可能会需要一些语法知识,这是我之前写的笔记Java异常 & 异常体系 (Java异常基础)
掌握最基本的语法,如 try-catch-finally 块,throw、throws 关键字, 自定义异常等之后,就可以在实践中逐步提高自己在典型场景中的应用了。

Java1.7引入了 try-with-resources,并且在JDK1.9的时候进行了改进,也算是JDK1.9的新特性之一了,我的JDK版本是1.8,不方便演示,菜鸟教程中很形象介绍了 try-with-resources 及其改进Java 9 改进的 try-with-resources
当然还有multiple catch,这是比较常见的多捕获异常



关于Exception异常的捕获,其实有几点是需要注意的:

  1. 尽量不要捕获类似 Exception 这样的通用异常,而是应该捕获特定异常。出现什么问题能够做到快速锁定,而且能够增强代码的可阅读性,一眼可以看出来处理的异常有哪些,换句话说,就是直观地体现出尽量多的信息
  2. 不要生吞(swallow)异常。这是异常处理中要特别注意的事情,因为很可能会导致非常难以诊断的诡异情况。生吞异常,往往基于假设这段代码可能不会发生,或者感觉忽略异常是无所谓的,这个一旦出问题了,是很糟糕的事情,不要在这里留漏洞。
  3. 不要在finally代码块中处理返回值
  4. 一个函数尽管抛出了多个异常,但是只有一个异常可被传播到调用端。最后被抛出的异常
    时唯一被调用端接收的异常,其他异常都会被吞没掩盖。如果调用端要知道造成失败的最初
    原因,程序之中就绝不能掩盖任何异常。
  5. 按照我们程序员的惯性认知:当遇到return语句的时候,执行函数会立刻返回。但是,在Java语言中,如果存在finally就会有例外。除了return语句,try代码块中的break或continue语句也可能使控制权进入finally代码块。这会是很折腾人的事情。请勿在try代码块中调用return、break或continue语句。万一无法避免,一定要确保finally的存在不会改变函数的返回值。
  6. 函数返回值有两种类型:值类型与对象引用。对于对象引用,要特别小心,如果在finally代码块中对函数返回的对象成员属性进行了修改,即使不在finally块中显式调用return语句,这个修改也会作用于返回值上。
  7. 勿将异常用于控制流。
  8. 如无必要,勿用异常。

***

异常是在执行某个函数时引发的,而函数又是层级调用,形成调用栈的,因为,只要一个函数发生了异常,那么他的所有的caller都会被异常影响。当这些被影响的函数以异常信息输出时,就形成的了异常追踪栈
异常最先发生的地方,叫做异常抛出点
涉及到异常抛出点,暂时没有能力把控它,今后能够做到深入理解的时候,一定会写一篇博文好好说一下的。


下面从性能方面介绍一下 Java 的异常处理机制:

  • try-catch 代码段会产生额外的性能开销,或者换个角度说,它往往会影响 JVM 对代码进行
    优化,所以建议仅捕获有必要的代码段,尽量不要一个大的 try 包住整段的代码;与此同
    时,利用异常控制代码流程,也不是一个好主意,远比我们通常意义上的条件语句
    (if/else、switch)要低效。
  • Java 每实例化一个 Exception,都会对当时的栈进行快照,这是一个相对比较重的操作。如
    果发生的非常频繁,这个开销可就不能被忽略了。