Java的异常处理
基本介绍
异常对象都是派生于Throwable类的一个实例,如果我们的内置的异常类不能满足需求,我们可以自定义异常进行处理
所有的异常都是由Throwable继承而来的,在下一层分为两个部分:
- Error类
- Exception类
其中,Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误
而我们重点关注的是Exception层次结构。这个层次结构又分解为了两个部分:
- RuntimeException
- 其他异常
划分标准为:由程序错误导致的异常属于RuntimeException;而我们的程序本身没有错误,但是由于像IO错误这类问题导致的异常属于其他异常。
通常,我们的RuntimeException包含以下:
- 错误的指令转换,数组的访问越界,访问null指针
不是派生于RuntimeException的异常包含以下:
- 试图在文件的尾部后面读取数据,试图打开一个不存在的文件,试图根据指定的字符查找Class对象
总结:
我们将Error和RuntimeException的所有异常称为非受查异常,而将所有的其他异常称为受查异常
异常的处理机制
对于异常,我们有两种处理机制:
-
在定义的方法上声明所有可能抛出的受查异常,而我们的非受查异常要么不可控制,要么就应该避免发生。如果方法没有声明所有可能发生的受查异常,编译器就会发出一个错误信息
-
捕获异常
继承中关于异常需要注意的点
如果我们的子类中覆盖了父类的一个方法,那么子类中声明的异常不能比父类声明的异常更通用,就是说子类方法中可以抛出的异常范围应该小于我们的父类,或者根本不抛出异常,如果我们的父类没有抛出异常,而必须保证我们的子类也不会抛出任何的受查异常。
异常的捕获
如果我们的异常发生的时候没有在任何地方进行捕获,那么程序就会终止执行,并在我们的控制台打印出我们的异常信息,其中就包括我们的异常的类型和堆栈的内容。此时我们可以使用捕获异常的方式进行处理:
try {
//代码块
}catch(Exception e) {
//异常的处理
}finally {
//资源的释放
}
其中的执行的流程:
-
如果我们的try语句块的任何代码抛出了一个在catch子句中说明的异常类,那么:
- 程序就会跳过try语句块的其余代码
- 程序将会执行catch子句中的处理器代码
-
如果我们的try语句块的任何代码抛出了一个在catch子句中没有声明的异常类,那么这个方法就会立即退出!!!
此时我们也可以使用在方法上声明需要抛出的异常:
public void readFile(String filename) throws IOException {
...
}
此时我们就会将这个异常进行传递,传递给调用这个方法的上层处理器进行处理,但是这个规则在有些场景中是不适用的:
- 父子类异常处理机制中,如果编写了一个覆盖了父类的方法,而这个方法又没有抛出异常,那么这个方法就必须捕获方法代码中的每一个受查异常。我们不允许子类的throws说明符中出现超过父类方法中所列出的异常类的范围。
捕获多个异常
我们可以同时捕获多个异常,即我们在一个try代码块种可以捕获多个异常类型,并对不同的类型的异常进行不同的处理。
捕获多个异常的时候,我们的异常变量隐含为final变量。
finally子句
当我们进行抛出一个异常,就会终止方法中剩余代码的处理,并退出这个方法的执行。此时我们可以使用finally子句进行资源的回收。
不管是否有异常发生,我们的finally子句中的代码都会被执行。
细节点:
-
当我们的finally子句中包含return语句的时候,将会出现一种意向不到的结果。假设此时我们得try语句块中有return语句,此时在方法返回之前,我们的finally子句的内容将会被执行。
public class TestFinally { public static void main(String[] args) { int res = find(2); System.out.println(res); } private static int find(int n) { try { int r = n * n; return r; }finally { System.out.println("finally 代码块"); } } } 输出结果: finally 代码块 4
-
如果上面的代码除了我们的try语句块中存在return语句,我们的finally子句中也存在一个return语句,此时我们finally语句中的return返回值将会覆盖原始的返回值
public class TestFinally { public static void main(String[] args) { int res = find(2); System.out.println(res); } private static int find(int n) { try { int r = n * n; return r; }finally { System.out.println("finally 代码块"); if (n == 2) return 0; } } } 输出结果: finally 代码块 0
堆栈轨迹(stack trace)
堆栈轨迹(stack trace)是一种方法调用过程的列表,它包含了程序执行过程中的方法调用的特定位置。
我们可以使用Thread.getAllStackTrace方法,它可以产生所有线程的堆栈轨迹。
异常代码测试
举例:我们的本地D盘下1.txt文件不存在,此时由于FileNotFoundException是IOException的子类,故匹配了FileNotFoundException的catch子句,只会打印e1,不会打印e2的异常信息
public class TestIO {
public static void main(String[] args) {
ObjectInputStream inputStream = null;
try {
inputStream = new ObjectInputStream(new FileInputStream("D:\\1.txt"));
Object object = inputStream.readObject();
} catch (FileNotFoundException e1) {
System.out.println("FileNotFoundException");
e1.printStackTrace();
} catch (IOException e2) {
System.out.println("IOException");
e2.printStackTrace();
} catch (ClassNotFoundException e3) {
System.out.println("ClassNotFoundException");
e3.printStackTrace();
}finally {
/*
* public interface ObjectInput extends DataInput, AutoCloseable
* ObjectInputStream底层实现了AutoCloseable接口,在try代码块退出的时候,会自动调用inputStream.close()方法
* */
}
}
}
输出结果:
FileNotFoundException
java.io.FileNotFoundException: D:\1.txt (系统找不到指定的文件。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileInputStream.<init>(FileInputStream.java:93)
at com.feng.io.TestIO.main(TestIO.java:15)
同时,我们的ObjectInputStream底层实现了ObjectInput接口,而我们的ObjectInput接口实现了AutoCloseable接口,它的内部包含一个close()方法,这个方法会声明为抛出一个IOException,它会在我们的try语句块退出的时候,自动的调用close方法,帮我们关闭资源。