Java的异常机制是一种识别和及时响应错误的一致性机制,java异常机制可以使程序中异常处理代码和正常业务代码分离,让代码更加优雅,提高程序健壮性。
(程序发生异常后,操作系统介入,会向程序发送信号,由程序来处理这些信号,具体参考 操作系统 章节)
异常的层次结构
Throwable包含了线程执行时的堆栈快照,提供诸如 printStackTrace() 等接口获取堆栈信息。
Error以及子类:程序无法处理的错误,此类异常发生,只能做好日志记录,然后进程退出。
Exception及子类:程序本身可以捕获和处理的异常,当然,也可以手动抛出。分为运行时异常和受检异常。
运行时异常和受检异常
受检异常在编译阶段发生,是编译器保障程序稳定性和安全性的一种手段,程序运行之前就预检出可能会发生的异常,程序中没有捕获相应的异常或者抛出异常的话,编译器就会停止编译工作,抛出错误信息。
运行时异常可以理解为编译器无法预测的异常类型,比如说使用for循环迭代数组时可能会发生的索引越界异常,这是在JVM内部发生的,如果最终没有对其进行捕获处理的话,发生异常的线程就会被干掉,如果是主线程的话,进程退出。
自定义异常类
很多时候,Java自身提供的异常类的错误描述信息并不能让我们满意,所以大多数的时候,往往捕获了一个异常之后,在 catch 语句块中抛出一个我们自定义的异常对象。
public class MyException extends Exception {
public MyException(){ }
public MyException(String msg){
super(msg);
}
}
自定义异常类也很简单,通过继承Exception类就可以了。
异常的捕获方式
private static void readFile(String filePath) {
try {
// code
} catch (FileNotFoundException e) {
// handle FileNotFoundException
} catch (IOException e){
// handle IOException
}
}
private static void readFile(String filePath) {
try {
// code
} catch (FileNotFoundException | UnknownHostException e) {
// handle FileNotFoundException or UnknownHostException
} catch (IOException e){
// handle IOException
}
}
可以捕获N多个异常信息,但是需要遵循一个原则:子类异常的 catch 语句块永远在父类异常的 catch 语句块之上。(原因自己思考)
try {
//执行程序代码,可能会出现异常
} catch(Exception e) {
//捕获异常并处理
} finally {
//必执行的代码
}
进程不崩溃,finally语句块是会执行的。
当然,也可以忽略掉catch语句块:
try {
//执行程序代码,可能会出现异常
} finally {
//必执行的代码
}
try (Scanner scanner = new Scanner(new FileInputStream("path"),"UTF-8")){
// code
} catch (IOException e){
// handle exception
}
我们常常在finally语句块中做资源释放的操作,比如说释放锁、文件资源描述符等,Java 7提供了一种更为简洁的方式:try-with-resource,使用该语法的前提是释放的资源类要实现 AutoCloseable 接口。
一些异常的实践
无法用条件判断的,才选择去捕获或者向上抛出
// 代码1
if(obj != null){
// ...
}
// 代码2
try{
obj.method();
} catch (NullPointerException e){
// ...
}
对于异常的捕获,JVM很少会去优化这些语句,并且整个异常的处理机制开销是比较大的,所以,能不用,尽量不用。
使用资源后,记得释放资源
这个老生常谈的话题了,可以使用finally语句块或者try-with-resource 语法。
给异常打好注释
方便调用者知道更多的信息,决定是否是继续向上抛还是处理。
优化不会具体异常
上文描述过:子类异常的 catch 语句块永远在父类异常的 catch 语句块之上
不要捕获Throwable类或者Error类,这不是你应该做的
不要对异常畸进行忽略,而是应该进行日志记录
包装异常时不要抛弃原始的异常
原始的异常对象中包含有堆栈信息快照,千万别忘记了!!!
不要再finally语句块中使用return
try语句块中的return语句成功后,并不会立即返回,而是等待finally执行完毕后,再返回。如果finally语句块中是使用了return,那么会覆盖掉之前return的内容。
(大概率就这些了,感兴趣的友友们可以去搜索一下字节码层面的实现,会涉及到一个叫做异常表的东西(Exception Table),意义不是很大)
2024.10.24
writeBy kaiven