java 在设计之初就提供了相对完善的异常处理体制,在有效使用异常的情况下,java异常可以清楚的回答 what -> 异常类型回答了什么被抛出 、where -> 异常堆栈跟踪回答了在哪抛出、why -> 异常信息回答了为什么被抛出 这三个问题;异常处理机制大大降低了编写和维护可靠程序的门槛;
异常体系结构
从概念角度分析
如上图所示:Throwable 是java 异常体系的顶层父类,他有两个子类,分别是Exception 和 Error 子类
- Error : 程序无法处理的系统错误,编译器不做检查 –> 系统错误、虚拟机错误、内存空间不足、方法调用栈溢出等
- Exception : 程序可以处理的异常、捕获后可能恢复
Exception 异常又主要分为两大类 ,分别是 RuntimeException 和 非RuntimeException
- RuntimeException:不可预知的,程序应当自行避免 -> 数组下标越界、访问空指针等;
- 非RuntimeException:即 Checked Exception 可以预知的,从编辑器校验的异常,从编译器角度讲是必须处理的异常,如果不处理程序就不能编译通过 -> 文件不存在而打开文件的 IOException 等;
从责任角度分析
- Error 属于 JVM 需要负担的责任
- RuntimeException 是程序应该负担的责任
- Checked Exception 可检查异常是 Java 编译器应该负担的责任
常见的 Error 以及 Exception
RuntimeException
- NullPointerException -> 空指针异常
- ClassCastException -> 类型强制转换异常
- IllegalArgumentException -> 传递非法参数异常
- IndexOutOfBoundsException -> 下标越界异常
- NumberFormatException -> 数字格式异常
非RuntimeException
- ClassNotFoundException -> 找不到指定 class 的异常
- IOException -> IO操作异常
Error
StackOverflowError -> 深递归导致栈被耗尽而抛出的异常
OutOfMemoryError -> 内存溢出异常
NoClassDefFoundError -> 找不到 class 定义的异常
1.类依赖的class 或者 jar 不存在
2.类文件存在,但是存在不同的域中
3.大小写问题,javac 编译的时候是无视大小写的,很有可能编译出来的 class 文件就与想要的不一样
异常处理机制
抛出异常
创建异常对象,交由运行时系统处理
当一个方法出现错误引发异常时,方法创建异常对象,并交付给运行时的系统,系统对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处理异常的代码并执行,之后会捕获异常。
捕获异常
寻找合适的异常处理器处理异常,否则终止运行
在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器,潜在的异常处理是异常发生时依次存留在调用栈中的方法的集合,当异常处理器所能处理的异常类型与抛出的异常类型相符时即为合适的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法直到找到含有异常处理器的方法并执行,当运行时系统遍历了调用栈都没找到合适的异常处理器,则运行时系统终止,java程序终止!
异常处理原则
1.具体明确:抛出的异常应能通过异常类名和Message 准确说明异常的类型和产生异常的原因
2.提早抛出:应尽可能早的发现并抛出异常,便于精确定位问题 (子类方法不能抛出比父类方法更大的异常)
3.延迟捕获:异常的捕获和处理应尽可能的延迟,让掌握更多信息的作用域来处理异常 即-> 抛给上层具体的业务类型来处理
异常处理关键字的使用
Java 的异常机制主要依赖于 try、catch、finally、throw和throws五个关键字。
throw 与 throws 关键字
throws 声明抛出只能在方法签名中使用,throws可以声明抛出多个异常类,多个异常类之间以逗号隔开。
1 | public class CheckedExceptionTest { |
运行结果
1 | Checked Exception -> a的值大于零,不符合要求 |
try - catch - finally
通过下面的程序我们发现,finally 块中的程序 总是有优先于 catch 块的 return 语句之前执行
1 | public static void main(String[] args) { |
运行结果
1 | ArithmeticException : java.lang.ArithmeticException: / by zero |
java 7 以及 java 9 的增强功能
java 7 的异常处理try块不在依赖于finally块来进行物理资源的关闭
try块可以单独存在 -> 对于自动关闭资源的try语句, 可以没有catch和finally
Java 9再次增强了try语句,Java 9不要求在try后的圆括号内声明并创建资源,只需要自动关闭的资源有final修饰或者是有效的final,Java 9允许将资源变量放在try后的圆括号内
1 | public static void main(String[] args) throws IOException { |
如何设计一个高效主流的异常处理框架
在用户看来,应用系统发生的所有异常都是应用系统内部的异常
- 设计一个通用的继承自 RuntimeException 的异常来统一处理
- 其余异常都统一转译上述异常 AppException
- 在 catch 之后,抛出上述异常的子类,并提供足已定位的信息
- 由前端接收 AppException 做统一处理
Java 异常处理消耗性能的地方
- try - catch 块影响 JVM 的优化
- 异常对象实例需要保存栈快照等信息,开销较大