当程序出错时,一个工作良好的程序应该能做到一下几点:1,向用户通告错误;2,保存所有的工作结果;3,允许用户以妥善的形式推出程序。
本章内容
- 处理错误
- 捕获异常
- 使用异常机制的技巧
- 使用断言
- 记录日志
- 调试技巧
1,处理错误
1.1 异常分类
异常处理的任务就是将程序的控制权从错误产生的地方转移给能够处理这种情况的错误处理器。
在java程序中,所有的异常对象都是继承Throwable类的实例。java异常继承树如图所示:
Error类层次结构描述了Java运行时系统的内部错误和资源耗尽错误,这种类型的错误不应该抛出,因为出现这种错误,除了通知用户,并尽量使程序安全退出之外,基本无能为力了。这类错误很少见。
Exception层次结构分为两个分支:RuntimeException和IOException。RuntimeException是由程序错误导致的异常;而程序本身没有问题,由像I/O错误或网络错误等问题导致的异常称为IOException。
**RuntimeException称为未检查错误,所有其它的错误称为已检查错误。**RuntimeException包括:
- 错误的类型转换
- 数组访问越界
- 访问空指针
IOException包括:
- 试图在文件尾部后面读取数据
- 试图打开一个不存在的文件
- 试图根据给定的字符串查找Class对象,而这个字符串表示的类并不存在。
如果出现RuntimeException异常,那一定是程序设计由问题
1.2 异常声明
方法应该在其首部声明所有可能抛出的异常。如果方法真的抛出了相应的异常对象,运行时系统就会开始搜索异常处理器,以便知道如何处理异常对象。异常声明的语法:
public FileInputStream(String name) throws FileNotFoundException
在自己写的方法中,比不将所有可能抛出的异常都进行声明。按以下四条原则抛出异常:
- 调用一个抛出已检查异常的方法
- 程序运行过程中发生错误,并且利用throw语句抛出一个已检查
- 程序出现错误,例如数组越界
- Java虚拟机和运行时库出现内部错误
对于前两种情况,必须告诉调用这个方法的调用者有可能抛出异常。
不需要声明Java的内部错误,即从Error继承的错误。任何程序代码都可能抛出这种异常,程序员对此没有控制能力
一个方法必须声明所有可能抛出的已检查错误,而未检查错误要么不可控制(Error), 要么就应该避免发生(代码本身的问题)。
如果子类中覆盖了超累的一个方法,那么子类方法中声明的已检查异常不能比超类中的更加通用;如果超类方法没有抛出任何异常,那么子类也不能抛出任何异常。
1.3 如何抛出异常
示例代码:
String readData(Scanner in) throws EOFException{
...
while(...){
if(!in.hasNext()){//EOF encountered
if(n < len){
throw new EOFException();
}
}
}
}
一旦方法抛出了异常,这个方法就不可能返回到调用者。
1.4 创建异常类
当所有的标准异常类都无法充分地描述问题,就可能需要创建自己的异常类。
示例代码:
class FileFormatException extends IOException{
public FileFormatException(){}
public FileFormatException(String gripe){
super(gripe);
}
}
自定义的异常类应该包含两个构造器,一个是默认的无参数构造器,另一个是带有详细描述信息的构造器。
2,捕获异常
如果某个异常发生的时候没有在任何地方进行捕获,那么程序就会终止,并在控制台打印出异常信息,其中包括异常的类型和堆栈的信息。
捕获异常使用try/catch语句块,示例代码如下:
try
{
code
more code
}catch(Exception e){
handler for this type exception
}
如果方法调用了一个抛出已检查异常的方法,就必须对它进行处理,或者将它继续进行传递。
通常,应该捕获那些知道如何处理的异常,而将那些不知道怎么处理的异常继续进行传递
如果方法是一个覆盖父类的方法,而这个方法又没有抛出任何异常,那么这个方法就必须捕获方法代码中出现的每一个已检查异常
2.1 捕获多个异常
在Java7中,可以在同一个catch子句中捕获多个异常类型,只有当捕获的异常类型彼此之间不存在子类关系时才需要使用这个特性。
try{
some code
}catch(FileFNotFoundException | UnkonwableHostException e){
handler
}
2.2 再次抛出异常与异常链
在catch子句中可以抛出一个异常,这样做的目的时为了改变异常的类型。
finally子句,finally子句中的代码不管异常是否被捕获都会被执行。finally子句通常被用于由资源需要关闭的场合。
带资源的try语句:
假设资源实现了一个AutoCloseable接口的类,可以使用一下的语句进行操作:
try(Scanner in = new Scanner(new FileInputStream("/usr/share/dict/words"))){
while(in.hasNext()){
System.out.println(in.next());
}
}
这个块正常退出或存在一个异常时,都会调用in.close()方法。使用分号可以同时打开多个资源。