彻底搞懂 java异常体系
异常处理框架概述以及优点
捕获错误最理想的是在编译期间,最好在尝试运行程序之前。但是,并非所有的错误能在编译期间检测到,有些问题总要在运行期间才能被发现和解决。如何让错误的生产者通过一种方式向接收者传递一些相关的信息,好让其知道问题的原因,进而正确的进行处理是一个优秀的异常框架的责任所在。
使用异常处理框架的一个好处是它往往能够降低错误处理代码的复杂度,可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程序健壮性。如果不使用异常处理,那么就必须检查特定的错误,并在程序中的许多地方去处理它。而如果使用异常,那就不必在方法调用处进行检查,因为异常机制将保证能够捕获这个错误。请看如下伪代码要实现拷贝的功能:
public static void main(String[] args) {
if("d://a.txt"这个文件夹存在){
if(e盘的空间 > a.txt的长度){
if(文件复制到一半io流断掉){
停止copy,输出问题;
}else{
//拷贝文件
copyFile();
}
}else{
System.out.println("空间不足");
}
}else{
System.out.println("文件不存在");
}
}
看这段伪代码,目的是为了拷贝文件到e盘,由于没有使用异常,所以要做很多的条件判断,并且将逻辑代码和处理错误代码杂糅在一起。
如何使用异常呢?
try{
//复制文件
copyFile();
}catch (Exception e) {
e.printStackTrace();
}
这种方式不仅节省代码,而且把“描述在正常执行过程中做什么事”的代码和“出了问题怎么办”的代码相分离。总之,与以前的错误处理方法相比,异常机制使代码的阅读、编写和调试工作更加井井有条。
异常的处理方式
Java将异常当做对象来处理,在执行一个方法时,如果发生异常,则这个方法生成代表该异常的一个对象,停止当前执行路径,并把异常对象交给JRE(Java Runtime Environment), JRE得到该异常后,寻找相应的代码来处理该异常。JRE在方法的调用栈中查找,从生成异常的方法开始回溯,一直到找到相应的异常处理代码为止。
异常的体系结构
java定义了一个Throwable类作为所有异常类的超类。java中定义了很多异常类,主要分为两类:Error和Exception
- 错误(error):当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。程序本身无法修复这些错误,都是一些比较严重的问题,大多数错误与代码编写者执行的操作无关,而表示代码运行时JVM出现的问题,如OutOfMemoryError、StackOverFlowError等,这些异常发生时,JVM一般会选则终止。error表明系统JVM已经处于不可恢复的崩溃状态。
- Exception:是程序本身能够处理的异常,如空指针、下标越界、类型转换等。Exception是所有异常类的超类,其子类对应了各种可能出现的异常事件,通常可分为:
-
运行时异常:RuntimeException:这类异常通常是由编程错误导致的,所以在编写程序时,并不要求必须使用异常处理机制处理这类异常,而是经常需要通过增加“逻辑处理来避免这些异常”,常见的运行时异常有:数组下标越界、空指针、被0除等
-
检查异常:CheckedException:所有不是RuntimeException的异常,统称为CheckedException,它强迫程序员处理异常,大大增强程序的可靠性。如IoException、SQLException等以及用户自定义的Exception异常,这类异常在编译时就必须做出处理,否则无法通过编译。
-
int a = 0;
//对于RuntimeException可以通过逻辑判断来避免异常的发生
if(a!=0){
a = 10/a;
}
String str = null;
//通过逻辑判断避免RuntimeException的发生
if(str!=null && !str.equals("")){
System.out.println(str.length());
}
//检查异常:IoException 必须包裹try/catch 或者抛出异常
try {
FileOutputStream fileOutputStream = new FileOutputStream(new File("d://a.txt"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Error和Exception的区别
Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;
Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。
其中检查异常是必须要处理的异常,当程序中出现这类异常时,要么使用try/catch捕获,要么使用throws语句向上抛出,否则编译无法通过。
非检查异常包括运行时异常以及error,编译器不要求强制处理,但我们在写代码的时候要考虑到这些情况进行预防。
异常处理的关键字
- try:用于监听。try后紧跟一个花括号括起来的代码块(花括号不可省略),简称try块,它里面放置可能引发异常的代码,当try语句块内发生异常时,异常就被抛出。
- catch: 用于捕获异常。catch后对应异常类型和一个代码块,用于处理try块发生对应类型的异常。
- throws:用在方法签名中,用于声明该方法可能抛出的异常。
- throw:用于抛出异常
- finally:用于清理资源,finally语句块总是会被执行。 多个catch块后还可以跟一个finally块,finally块用于回收在try块里打开的物理资源(如数据库连接、网络连接和磁盘文件)。只有finally块执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
最后附上一个综合性的使用案例
public static void main(String[] args) {
FileReader reader = null;
try {
//检查异常,需要使用try/catch 或者 throws抛出 此处选择try/catch
reader = new FileReader("d:/b.txt");
//这里也是检查异常 继续使用try/catch 所以下面有俩catch块儿
//需要注意的是子类异常在父类异常前面catch
char read = (char) reader.read();
System.out.println(read);
//子类异常在父类异常前面
//FileNotFoundException是IOException的子类
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
//finally中进行资源的关闭close
} finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
或者使用throws抛出
转载自:https://juejin.cn/post/6965515719454752781