《第7章 异常、断言和日志》
异常
1.异常分类
在Java中,异常对象都是派生于Throwable类的一个实例。
如果Java内置的异常类不能够满足需求,用户可以创建自己的异常类
需要注意的是所有异常都是由Throwable继承而来,但在下一层立即分解为两个分支
Error和Exception
Error类层次结构描述了Java运行时系统的内部错误和自愿耗尽错误,应用程序不应该抛出这种类型的对象。
在Java程序设计时,需要关注Exception层次结构
Exception层次结构又分解为两个分支:
一个分支派生于RuntimeException
另一个分支包含其他异常
划分这两个分支的规则为:
由程序错误导致的异常属于RuntimeException
程序本身没有问题,但由于像I/O错误这类问题导致的属于其它异常
一般来说,属于RuntimeException的有:
- 错误的类型转换
- 数组访问越界
- 访问null指针
属于其它异常的有:
- 试图在文件尾部后面读取数据
- 试图打开一个不存在的文件
- 试图根据给定的字符串查找class对象,而这个字符串表示的类并不存在
Java将派生于Error类或RuntimeException类的所有异常称为非受查异常
所有其他的异常称为受查异常
而编译器将核查是否为所有的受查异常提供了异常处理器
2.声明受查异常
如果遇到了无法处理的情况,那么Java的方法可以抛出一个异常
方法应该在其首部声明所有可能抛出的受查异常
不需要声明可能抛出的非受查异常
因为非受查异常要么不可控制(Error),要么应该避免发生(RuntimeException)
例如,我们读取文件常用到的类FileInputStream的read函数
其read构造函数的定义如下:
public int read() throws IOException {
return read0();
}
这个read函数的声明表示如执行成功将返回int类型的数据
但也有可能抛出一个IOException异常
同时由于该异常为受查异常,所以必须为其提供异常处理器
怎么调用一个可能会抛出受查异常的方法呢?
调用的方式一般有两种:以FileInputStream打开文件为例
一种是在方法中直接使用throws语句来继续传递异常
public void readFile() throws FileNotFoundException{
FileInputStream fileInputStream = new FileInputStream("hello.txt");
...
}
特别需要说明的是,如果在子类中覆盖了超类的一个方法
如果超类方法没有抛出任何受查常
子类也不能抛出任何受查异常
另一种则是使用try-catch语句将其捕获并处理
try {
FileInputStream fileInputStream = new FileInputStream("hello.txt");
...
}
catch (FileNotFoundException e){...}
3.如何抛出异常
一个简单的例子:一个名为readData的方法读取一个文件
当读取的字符数(n)小于文件的总字符数(len)时抛出异常
String readData(Scanner in) throws EOFException{
...
while(...){
if(!in.hasNext()){
if(n<len) throw new EOFException();
}
...
}
}
其中EOFException是已经存在的异常类型,若是我们想要抛出属于我们自己的异常类型
可以新建一个类继承于Exception类然后再使用throw关键词抛出即可
4.捕获异常
要想捕获一个异常,必须设置try/catch语句
一般的try/catch语句如下:
try {
...
}
catch (ExceptionType e){
...
}
这里需要注意的是:
如果方法中的任何代码抛出了一个在catch子句中没有声明的异常类型
那么这个方法将立即退出
在try语句块中可以捕获多个异常类型
并对不同的异常类型做出不同的处理
try{
...
}
catch(FileNotFoundException e){...}
catcg(UnkownHostException e){...}
catch(IOException e){...}
可以使用e.getMessage()得到更为详细的信息
可以使用e.getClass().getName()得到异常对象的实际类型
当然,我们也可以选在在catch语句中再次抛出异常
这样做的目的一般是为了改变异常的类型
try/catch语句一般也搭配finally使用
不管有无异常被捕获,finally子句的代码都会被执行
5.使用异常机制的技巧:
- 异常处理不能代替简单的测试
- 不要过分地细化异常;如不要将一条语句封装在一个独立的try语句中
- 利用异常层次;
- 不要压制异常;
- 在检查错误时,“苛刻”要比放任好
- 不要羞于传递异常
断言
断言机制允许在测试期间想代码插入一些检查语句。当代码发布时,这些插入的测试语句将被自动地移走
Java引入了关键字assert,对应的有两种形式
assert 条件;
assert 条件:表达式;
这两种形式都会对条件进行检测,若结果为false,则抛出一个AssertError异常。
在第二种形式中,表达式将会被传入AssertError的构造器,并转换成一个消息字符串
在默认情况下,断言被禁用,需要开启。
什么时候使用断言呢?
- 断言失败是致命的,不可恢复的错误
- 断言检查只用于开发和测试阶段。断言只应该用于在测试阶段确定程序内部的错误。
记录日志
每个Java程序员都很熟悉在有问题的代码中插入System.out.println方法调用来帮助观察程序运行的过程,
而一旦发现问题的根源,就要将这个打印语句删除。若其他地方还有问题,则需要重复这种写打印语句又删除的行为。
而记录日志则提供了一种更为科学的方法。
要生成简单的日志记录,可以使用全局日志记录器并调用其info方法:Logger.getGlobal().info("File->Open menu item seleceted");Logger.getGlobal().info("x"+x);//输出某个变量的值
但是在适当的地方(如main开始)调用Logger.getGlobal.setLevel(Level.OFF);
就会取消所有的日志。
在一个专业的应用程序中,不要将所有的日志都记录到一个全局日志记录器中,而是可以自定义日志记录器
可以调用getLogger方法来创建或者获取记录器private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp");
用一个静态变量存储日志记录器的引用
记录日志的常见用途是记录那些不可预料的异常