Java 异常知识
1. 异常的两大关键因素
(1)抛出异常 1. 显式:应用程序手动抛出异常。具体就是使用 throw 抛出异常 2. 隐式:Java 虚拟机对于无法执行的代码,自动抛出异常 (2) 捕获异常 1.try 代码块:用来标记需要进行异常监控的代码。2.catch 代码块:跟在 try 代码块之后,用来捕获在 try 代码块中触发的某种指定类型的异常。除了声明所捕获异常的类型之外,catch 代码块还定义了针对该异常类型的异常处理器。在 Java 中,try 代码块后面可以跟着多个 catch 代码块,来捕获不同类型的异常。Java 虚拟机会从上至下匹配异常处理器。因此,前面的 catch 代码块所捕获的异常类型不能覆盖后边的,否则编译器会报错。3.fnally 代码块:跟在 try 代码块和 catch 代码块之后,用来声明一段必定运行的代码。它的设计初衷是为了避免跳过某些关键的清理代码,例如关闭已打开的系统资源。在程序正常执行的情况下,这段代码会在 try 代码块之后运行。否则,也就是 try 代码块触发异常的情况下,如果该异常没有被捕获,fnally 代码块会直接运行,并且在运行之后重新抛出该异常。如果该异常被 catch 代码块捕获,fnally 代码块则在 catch 代码块之后运行。在某些不幸的情况下,catch 代码块也触发了异常,那么 fnally 代码块同样会运行,并会抛出 catch 代码块触发的异常。在某些极端不幸的情况下,fnally 代码块也触发了异常,那么只好中断当前 fnally 代码块的执行,并往外抛异常。
2. 异常的分类
1. 所有异常的父类都是 Throwable
2.Error 异常是程序的执行状态无法恢复的状态,只能中止线程甚至中止 JVM 的异常
3.Exception 是相对 Error 没有这么严重的异常
4.Runtime Exception 和 Error 都属于不需要检查的异常
5. 除了 Runtime Exception 和 Error 的异常都是 Check Exception 异常
6.Check Exception 异常都是需要显式捕获的异常
3.Java 虚拟机是如何捕获异常的?
java 虚拟机构造异常实例非常昂贵。虚拟机需要生成该异常的栈轨迹。该操作会逐一访问当前线程的 Java 栈帧,并且记录下各种调试信息,包括栈帧所指向方法的名字,方法所在的类名、文件名,以及在代码中的第几行触发该异常。既然异常实例的构造十分昂贵,我们是否可以缓存异常实例,在需要用到的时候直接抛出呢?从语法角度上来看,这是允许的。然而,该异常对应的栈轨迹并非 throw 语句的位置,而是新建异常的位置。因此,这种做法可能会误导开发人员,使其定位到错误的位置。这也是为什么在实践中,我们往往选择抛出新建异常实例的原因。
异常处理器 1. 来源:每个方法在编译的时候都会生成一个异常表。异常表里面的每一个条目都代表一个异常处理器。2. 组成:(1)from 指针,to 指针:代表捕获异常的范围,就是 Try 的范围。(2)target 指针:代表处理器的开始位置,就是 catch 的起始位置。(3)捕获的异常类型。3. 捕获异常 (1) 当程序触发异常时,Java 虚拟机会从上至下遍历异常表中的所有条目。当触发异常的字节码的索引值在某个异常表条目的监控范围内,Java 虚拟机会判断所抛出的异常和该条目想要捕获的异常是否匹配。如果匹配,Java 虚拟机会将控制流转移至该条目 target 指针指向的字节码。(2)如果遍历完所有异常表条目,Java 虚拟机仍未匹配到异常处理器,那么它会弹出当前方法对应的 Java 栈帧,并且在调用者(caller)中重复上述操作。在最坏情况下,Java 虚拟机需要遍历当前线程 Java 栈上所有方法的异常表。4.finally 代码的编译:当前版本 Java 编译器的做法,是复制 fnally 代码块的内容,分别放在 try-catch 代码块所有正常执行路径以及异常执行路径的出口中。
代码 1:
Try{
Try block
} catch {
Catch block
} finally {
Finally block
}
代码 2:
Try {
Try block
Finally block
} catch {
Catch block
Finally block
} finally{
Finally block
}
代码 1 是我们的 Java 代码,代码 2 是编译之后的 Java 代码。注意:如果 catch 代码块捕获了异常,并且触发了另一个异常,那么 fnally 捕获并且重抛的异常是哪个呢?答案是后者。也就是说原本的异常便会被忽略掉,这对于代码调试来说十分不利。
5.Java7 的 Supressed 异常以及语法糖
针对上节说的会将 catch 的异常忽略掉,Java7 引入了 Supressed 异常处理这个问题。但是使用起来还是很麻烦(没有感受,????)。为此,Java 7 专门构造了一个名为 try-with-resources 的语法糖,在字节码层面自动使用 Supressed 异常。当然,该语法糖的主要目的并不是使用 Supressed 异常,而是精简资源打开关闭的用法。资源的关闭操作本身容易触发异常。所以为了让每一个资源都关闭,所以每一个资源都要写个 try catch 这样太过繁琐,所以 try-with-resources 就很好的解决了这个问题。程序可以在 try 关键字后声明并实例化实现了 AutoCloseable 接口的类,编译器将自动添加对应的 close() 操作。在声明多个 AutoCloseable 实例的情况下,编译生成的字节码类似于上面手工编写代码的编译结果。与手工代码相比,try-with-resources 还会使用 Supressed 异常的功能,来避免原异常“被消失”。除了 try-with-resources 语法糖之外,Java 7 还支持在同一 catch 代码块中捕获多种异常。实际实现非常简单,生成多个异常表条目即可。