Exception(异常)是Java中提高程序健壮性必不可少的机制。在程序出现(由于用户操作产生的)错误时,我们使用抛出异常的方式及早地进行处理,减少其对程序的破坏性,进而提升程序的健壮性。
一、异常类的继承层次结构
上图是所有异常类继承关系结构简图。其中Throwable是所有异常类的祖先,包括以下两大类:
1.Error(错误)
Error有时也被译作系统异常,是由系统内部引起的,对于这类异常程序员通常不需要处理,也无法处理。除此之外还有可能是编译过程中抛出的,提醒程序员更正代码语法错误。
2.Exception(异常)
Exception是我们常常所指的狭义的异常,是由程序错误引起的,也是程序员所需要关注的。其主要分为RuntimeException(运行时异常)和其他类的异常。
其中RuntimeException通常是程序员自身错误导致的,比如图中提到的空指针异常,数组越界异常等。RuntimeException和Error被合称为UncheckedException(不受检查异常,顾名思义,这些异常不是必须进行捕获处理)。
而剩余其他的异常则主要是由用户引起的,被称为Checked Exception(受检查异常),必须要进行捕获和处理。Checked Exception是提高程序健壮性最重要的部分。
二、异常的抛出与捕获处理
有关异常的抛出、捕获和处理,涉及5个关键词——throw,thorws,try,catch,finally。五个关键词用法先看代码:
void fun1(){
try{
fun2();
}catch(Exception e){
e.printStack();
}finally{
……
}
}
void fun2() throws Exception{
throw new Exception("massage");
}
- throw:在你认为程序出现异常时,用throw抛出一个Exception(或其子类)的实例。
- throws:用于异常声明,写在方法签名后方,表明此方法可能抛出的异常。
- try:在try语句块中调用可能抛出异常的方法
- catch:捕获try语句块中抛出的相应异常,并执行catch语句块中的代码。一个try语句块后可以跟多个catch语句块用于捕获不同的异常。其判断逻辑类似if-else if-else,再捕获具有继承关系的两个异常时,一定要把子类写在前面。此外,新版本java还支持“|”符号表示“或”,如:
catch(Exception1|Exception2 e)
。 - finally:无论try语句块中的代码是否抛出异常,异常是否被catch捕获,程序最终都会执行finally中的代码,即使程序向上抛出异常或者已经return。可用于关闭资源等。
对于一个可能抛出Checked Exception的方法,调用者只有两个选择——用try-catch语句块进行捕获处理,或是用throws声明,继续向上抛出。否则会编译不通过。而Unchecked Exception不需要捕获,并且通常也不应该捕获,因为此类异常是正确的程序应该避免出现的。在程序中捕获Unchecked Exception不会报错,但这是一种不好的习惯,是一种“掩耳盗铃”。
在考虑应该抛出Unchecked还是checked异常时,考虑这样一个问题——Exception抛出后,Client能否进行补救进而恢复异常?如果能,则抛出Checked异常并进行捕获处理,否则抛出Unchecked异常。
补充说明:
- 当某个方法被重写时,子类方法可以抛出与父类方法相同的的异常,也可以抛出比父类方法更具体的异常(子类)或不抛出异常。
- 关键词try后可跟一括号,在其中声明一个或多个资源变量,try-catch语句块中代码执行完毕后自动关闭该资源。如
try(Resource r = new Resource()){...}
,被称为try-with-resources结构。此结构类似Python中的with…as…用法
三、自定义异常
通常只需要继承现有的Exception或其子类,并按需求重写构造方法即可。也可以增加其他方法。