异常分类
所有的异常都是由Throwable继承而来,又被分为Error类和Exception;Error类描述了Java运行时系统的内部错误和资源耗尽错误,这种异常在我们的控制范围之外,我们只能尽可能妥善地终止程序。
我们应重点关注的是Exception层次结构,它有两个分支:IOException和RuntimeException;一般来说,由编程错误导致的异常属于RuntimeException,派生于RuntimeException的异常可能包括错误的强制类型转换,数组访问越界,访问null指针等。当出现RuntimeException时,基本上是程序员编程错误所导致的。而派生于IOException的异常包括试图超越文件末尾继续读取数据,试图打开一个不存在的文件,试图根据给定的字符串查找类对象,而这个字符串表示的类并不存在等。
派生于Error和RuntimeException的异常称为非检查型,我们一般不声明非检查型异常,也不提供异常处理器(当程序出现编程错误时,应该去检查代码的逻辑与语法等问题,利用spotbug等工具检查修正,而不应仅仅向客户端抛出异常)。所有不派生与Error和RuntimeException的异常称为检查型异常,编译器将会检查我们是否为所有的检查型异常提供了异常处理器。
在方法中用throws声明异常
在遇到以下四种情况时会抛出异常:1.调用了一个抛出检查型异常,例如FileInputStream构造器;2.检测到一个错误,并且利用throw语句抛出一个检查型异常;3.程序出现错误;4.Java虚拟机或运行时库出现内部错误。如果出现前两种情况必须告诉客户端可能抛出异常,当然也可以选择将异常捕获并处理,这样就可以不用向客户端抛出异常。如果能找到一个合适的异常类满足要抛出异常的类型,则我们创建该类的一个对象并将对象抛出,一旦方法抛出了异常,这个方法就不会再返回到调用者。也就是说,不必操心建立一个默认的返回值或错误码;如果遇到任何标志异常类都无法描述清楚的问题,我们就需要创建一个属于自己的异常类,它必须派生于Exception或者Exception的子类。
需要注意的是,子类方法中声明的检查型异常不能比父类/超类方法中声明的异常更通用,即子类方法中声明的检查型异常需要是超类中声明的异常的子类,当超类中该方法没有抛出检查型异常时,子类中该方法也不应抛出异常。
捕获异常
要想捕获一个异常,需要设置try/catch语句。
try{
code
}
catch(ExceptionType e){
handler for this type
}
finally{
code
}
如果try语句中的任何代码抛出了catch子句中指定的一个异常类,那么程序将跳过try语句块的其余代码,并执行catch子句中的处理器代码。如果抛出了一个catch中没有声明的异常,那么将传递给调用者处理。编译器严格执行throws说明符。如果调用了一个抛出检查型异常的方法,就必须处理这个异常或者继续传递这个异常。一般来说,我们捕获那些知道如何处理的异常,继续传播那些我们不知道如何处理的异常。不管有没有异常被捕获,finally中的代码都会被执行,所以finally中通常会对资源进行清理,例如使用in.close关闭输入流。
使用异常的一些技巧
1.异常处理不能代替简单的测试,捕获异常的时间花费远超完成简单测试的花费,因此使用异常的基本规则是:只在异常情况下使用异常
2.不要过分地细化异常。这会导致代码量的急剧膨胀,我们只需满足异常处理的承诺:将正常处理和错误处理分开
3.充分利用异常层次结构。
不要只捕获Throwable等异常,这会使代码可读性差难以维护,应寻找一个适合的子类 或者自己创建异常类。
考虑检查型异常和非检查型异常,检查型异常本来就很庞大,不要为逻辑错误抛出这些 异常
如果能将一种异常转换为另一种更加适合的异常,那么应当进行转换。
4.不要压制异常。如果没有声明throw某个异常,那么当这个异常出现时编译器就会报错,我们应适当地对异常进行处理
5.在检查错误时,应保持苛刻的态度
6.不要羞于传递异常,更高层的方法通常可以更好地通知用户发生了错误,或者放弃不成功的命令。5和6可以归纳为“早抛出,晚捕获”



