第十二章 通过异常处理错误
Java基本理念:结构不佳的代码不能运行
编译期间并不能找出所有错误,余下的问题必须在运行期间解决
12.1 概念
将执行过程中做什么事和出了问题怎么办的代码相分离
12.2 基本异常
抛出异常后
- 在堆上new一个异常对象
- 当前的执行路径被终止,弹出异常对象的引用
- 异常处理机制接管程序,寻找一个恰当的地方来继续执行程序
说白了,异常的好处就是程序的某一部分失败了,异常将恢复到程序的某个已知的稳定点上
12.2.1 异常参数
标准异常类有两个构造器,一个是默认构造器,另一个接受字符串参数,以便把相关信息放入异常对象的构造器,将这个字符串内容提取出来有多重不同的方法
12.3 捕获异常
12.3.1 try块
这个没啥可说的
12.3.2 异常处理程序
用catch语句处理
异常处理一般有终止和恢复两种模型,一般使用终止模型,恢复模型不是很实用
12.4 创建自定义异常
自己定义异常类,必须从已有的异常类继承,异常最重要的就是类名
没什么多说的,就是两种构造器,一种默认构造器,另一种是接受一个String的构造器
练习1、try块抛出异常,catch捕获异常并打印参数,finally证明这里确实得到了执行
练习2、捕获了一个空指针异常
练习3、数组越界异常,ArrayIndexOutOfBoundException
练习4、略
练习5、while循环建立恢复模型,捕获到异常continue,直到捕获不到异常了break;
12.4.1 异常与记录日志
大部分客户端程序员只是看一下抛出的异常类型,对异常所添加的其他功能也许根本用不上
练习6、使用Logger.getLogger()创建Logger对象,自动记录日志
练习7、还是日志记录的使用,同上
12.5 异常说明
方法可能抛出的异常礼貌的告诉客户端程序员,在方法的形参列表后紧跟throws
如果方法里的代码产生了异常而没处理,要么处理,要么表明此方法将产生异常
声明方法抛出异常,实际上不抛出,为异常先占个位子,以后抛出这种异常就不用修改代码,在定义抽象类和接口的时候这种能力很重要,派生类和接口实现类就能够抛出这些预先声明的异常
练习8、在某方法抛出异常,在使用该方法的地方用try-catch测试该异常
12.6 捕获所有异常
通过使用异常的基类Exception,可以捕获所有的异常,可以调用从Throwable继承的getMessage(),getLocalizedMessage(),toString()等方法获取详细信息
printStackTrace()方法可以打印调用轨迹,也就是异常抛出的地方的一个序列
也可使用getClass()来返回一个此对象类型的对象
练习9、略
12.6.1 栈轨迹
e.getStackTrace()可以直接返回一个由栈轨迹元素构成的数组
0位置是异常的根源,最后位置是一开始调用的方法
12.6.2 重新抛出异常
如果捕获了异常,去抛出这个异常,后续的catch子句将被忽略,并且printStackTrace()方法显示的是原来异常抛出点的栈信息,如果想更新此信息,调用fillInStackTrace()方法
12.6.3 异常链
想要在捕获一个异常后抛出一个新的异常,希望把原始异常的信息保存下来,这就是异常链
练习10、这个练习告诉我们,捕获了一个异常抛出另一个新的异常,之前的异常信息将丢失
练习11、通过包装了一个RuntimeException,抛出了一个新的异常
12.7 Java标准异常
Throwable可分为两种类型,一种是Error,编译时和系统错误,一般不用关系,另一种就是我们的重点Exception
12.7.1 特例:RuntimeException
运行时异常,我们常遇到的NullPointerException和ArrayIndexOutOfBoundsException就是集成RuntimeException,RuntimeException不需要在声明方法后面去抛出RuntimeException
练习12、自定义一个异常继承自RuntimeException,如果超出数量直接throw这个异常
12.8 使用finally进行清理
无论try块中的异常是否抛出,都能执行一些代码,那么需要finally
当Java中的异常不允许我们回到异常抛出点,就把try块放到循环当中
12.8.1 finally用来做什么
恢复到初始状态,除内存外的一些东西,比如已打开的文件或网络连接等
练习13、略
练习14、略
练习15、略
12.8.2 在return中使用finally
在try块中return依然会执行finally,所以对于finally来说,在哪里返回无关紧要
练习16、证明了在try中return,仍然会执行finally中的代码
练习17、同上
12.8.3 缺憾:异常丢失
如果try块中的代码抛出异常,finally中的代码也抛出异常,那么try块中的异常会被finally中的异常取代,try块的异常丢失了
还有一种是在finally中return,会抛出异常,但是不会有任何输出
练习18、略
练习19、解决上述异常被取代的问题,finally中的代码也去try-catch,先打印此异常,然后之前的异常就会被正常catch
12.9 异常的限制
子类方法不能抛出基类方法定义以外的异常
父类方法没有声明异常,子类抛出异常编译就会失败
子类构造器不能捕获父类构造器抛出的异常
练习20、略
12.10 构造器
编写可能出现异常的构造器要小心,必要的时候嵌套try-catch语句
练习21、子类构造器无法捕获父类构造器抛出的异常
练习22、略
练习23、略
练习24、略
12.11 异常匹配
简单点说,如果在第一个catch子句中捕获了异常,那么接下来的catch子句都不会执行了
练习25、略
12.12 其他可选方式
这一小节理论比较多,大体说了
尽量不要吞食异常,也就是try了异常catch是空处理
不行就把异常包装成RuntimeException
练习27、try-catch一个异常后throw RuntimeException(e)
练习28、继承自RuntimeException的异常,编译器允许省略try-catch块
练习29、略
练习30、略
12.13 异常使用指南
应该在下列情况下使用异常
- 在知道如何处理异常的时候才捕获它
- 解决问题并且重新调用产生异常的方法
- 少修少补,绕过异常发生的地方继续执行
- 用别的数据进行计算,以代替方法预计会返回的值
- 把当前环境下能做的事情做完,然后把异常抛向更高层
- 终止程序
- 进行简化
- 让类库和程序更安全
12.14 总结
异常的优点之一就是:你可以集中精力处理你要解决的问题,而在另一处处理你编写的这段代码中的错误。
可能只有10%的程序从错误中恢复了,但是“报告”是异常的精髓所在