异常分类
异常对象都是派生于Throwable类的一个类实例
在下一层立即分解为了两个分支:Error和Exception
- Error:该类层次结构描述了Java运行时系统的内部错误或资源耗尽错误,应用程序不应该会抛出这种类型的对象,如果出现了这种对象,除了终止程序、通知用户之外,其他都做不了
- Exception:该类层次结构描述了异常,一般是由于设计有误、代码错误和用户输入有误产生的
Exception
Exception结构又分解为两个分支,一个分支派生于RuntimeException;另一个分支包含其他异常
由编程产生的错误属于RuntimeException,也就是代码的问题(如空指针、越界、错误的强制类型转换),而不是其他,比如文件不存在,比如SQL不规范,这些都不属于RuntimeException
派生于RuntimeException的异常包括以下问题
- 错误的强制类型转换
- 数组访问越界
- 访问Null指针
不是派生于RuntimeException的异常包括
- 试图超越文件末尾继续读取数据
- 试图打开一个不存在的文件
- 试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在
Java将派生于Error类或RuntimeException类的所有异常称为非检查型异常,所有其他的异常称为检查型异常
异常处理方式
抛出异常
捕捉异常
捕捉异常使用的是try-catch语句
只要在try语句块中的任何代码抛出了catch子句中指定的一个异常类,那么就会执行下面的操作
- 程序将跳过try语句块的其余代码
- 执行catch子句中的处理器代码
如果try语句块中的代码没有抛出异常,那么程序就会跳过所有catch子句
通常来说,catch处理器最好的选择是:什么也不做,而是将异常传递给调用者,也就是让调用该方法的人去操心这个异常问题
看一下几个捕捉到的异常对象的函数
- e.getMessage:获取异常对象的信息
- e.getClass:获取异常对象的class类型
可以捕捉多个异常也可以对捕捉到的多个异常进行合并
假如对几个异常处理的动作是一样的,我们就可以进行合并,但合并的时候,要注意合并的异常类型彼此之间是不可以存在继承关系的,因为异常变量隐含为final变量
合并异常可以让代码看起来比较简单,而且生成的字节码文件只有公共catch代码块
可以看到,当合并的异常具有继承关系的时候,会提示重复捕捉了,只需要捕捉父类异常即可。
当然,我们也可以同时捕捉多个异常
再次抛出异常与异常链
前面提到过,catch块最好的处理就是什么都不做,然后返回给调用者进行处理
针对这个,我们也可以在catch块中再次进行抛出异常,让上一层的调用者进行捕捉,然后进行处理,也可以通过这种方式来改变抛出异常的类型
不过还有更好的处理方式,可以把原始异常设置为新异常的原因,即调用e.initCause方法
在上一层,我们可以去获取原因
finally子句
有时候,代码抛出一个异常的时候,try剩下的代码就不会再执行,并且退出这个方法,那如果有资源要进行释放怎么办,这些资源无论有没有异常抛出,最后都要进行释放,那么就可以使用finally子句
不管是否有异常被捕获,finally子句中的代码都会执行
那么假如在finally中return会发生什么?
可以看到,结果返回的是finally里面的值,也就是finally语句块会提前return,从而阻止了try或者catch的return
那么如果catch抛出异常,会不会直接给上一层给捕捉到呢?
答案是不会的,仍然会执行finally,可以理解成finally会在结束try或者catch的时候调用,调用完finally再结束try或者catch
try-with-Resources语句
当要关闭资源的时候
结果如下
也可以对多个资源进行操作
当然try-with-Resources语句自身也是可以有catch子句,同理也可以有自己的finally子
这些子句都会在关闭资源之后执行,那如果finally提前Return,也会去执行吗?
可以看到,资源的close方法也会在finally之前去进行
断言
断言的关键字为assert,这个关键字有两种形式
//第一种
assert condition;
//第二种
assert condition:x;
断言默认是关闭的,要进行手动去开启
断言的目的:如果condition表达式返回的是false,那么就会产生一个AssertionError
而第二种的断言格式,其实就是调用了AssertionError的构造方法,将出错的信息封装在了AssertionError里面