第七章 异常、断言和日志
-
处理错误
-
捕获异常
-
使用异常的技巧
-
使用断言
-
日志
-
调试技巧
当出现异常时,要做到以下几点
- 向用户通知错误
- 保存所有的工作
- 允许用户妥善的退出程序
7.1 处理错误
如果由于出现错误而使的某些操作没有完成,程序应该:
- 返回一种安全状态,并能够让用户执行其他命令;或者
- 允许用户保存所有工作的结果,并以妥善的方式终止程序。
异常处理的任务就是将控制权从产生错误的地方转移到能够处理这种错误的处理器。
异常有自己的语法和特定的继承层次结构。
7.1.1 异常分类
java语言中,异常对象都是派生于Throwable类的一个类实例。Throwable的下一层是Error和Exception
1.Error类层次结构描述了Java运行时系统的内部错误和资源消耗错误。
2.要重点关注Exception层次结构,这个层次结构又分解为两个分支:
一个分支派生于RuntimeException;由编程错误导致的异常属于RuntimeException
另一个分支包含其他异常。如果程序本身没有问题,但由于I/O错误这类问题导致的错误属于其他异常
派生于RuntimeException的异常包含以下问题:
- 错误的强制类型转换
- 数组访问越界
- 访问null指针
不是派生于RuntimeException的异常包括:
- 试图超越文件末尾继续读取数据
- 试图打开一个并不存在的文件
- 试图根据给定的字符串查找Class对象,而这个字符表示的类并不存在。
java语言规范将Error和RuntimeException类的所有异常称为非检查型异常,所有其他的异常成为检查型。
编译器将会检查你是否为所有的检查型异常提供了异常处理器。
7.1.2声明检查型异常
在以下四种情况是,抛出异常
- 调用了一个抛出检查型异常的方法
- 检测到一个错误,并且利用throw语句抛出一个检查型异常
- 程序出现错误,例如:a[-1]=0 会抛出一个非检查型的异常
- Java虚拟机或运行时库出现内部错误
一个方法必须声明所有可能throws抛出的检查型异常,不应该抛出非检查型异常,
因为对于Error类异常,任何代码都有可能抛出这类错误,
对于RuntimeException类异常,完全是在我们的控制之中的,我们应该花更多的时间修正这种异常。
如果一个已有的异常类能够满足你的要求,抛出这个异常十分容易
一旦方法抛出了异常,这个方法就不会返回到调用者
- 找到一个合适的异常类
- 创建这个类的对象
- 将对象抛出
7.2 捕获异常
7.2.1 捕获异常
try{
code
more code
}catch(Exception e){
handler for this type
}
如果try语句块中的任何悍马抛出了catch子句中指定的一个异常类,那么
1.程序将跳过try语句块中的的其他代码
2.程序将执行catch子句中的处理器代码
如果方法中的任何代码抛出了catch子句中没有声明的异常类型,那么这个方法就会立即退出
异常对象可能包含有关异常性质的信息。要想获得这个对象的更多信息,可以尝试使用
e.getMessage()
得到详细的错误信息(如果有的话)。或者使用
e.getClass().getName()
得到异常对象的实际类型
7.2.4 finally子句
作用是清理一些资源,不管是否有异常被捕获,finally子句中的代码都会执行
在三种情况下执行finally
- 代码没有抛出异常
- 代码抛出一个异常,并在catch中捕获
- 代码抛出一个异常,但没有任何一个catch子句捕获
finally子句要用于资源的清理,不要把改变控制流的语句(return,throw,break,continue)放在finally子句中。
7.2.5 try-with-Resources 语句
// try-with-resources 语句(带资源的try语句)
try(Resources res = ...){
work with res
}
// try块退出时,会自动调用re.close(),就像使用了finally块一样
7.2.6 分析堆栈轨迹元素
堆栈轨迹(stack trace)时程序执行过程中某个特定点上所有挂起的方法调用的一个列表
7.3 使用异常的技巧
- 异常处理不能代替简单的测试
- 不要过分的细化异常
- 充分利用异常层次结构
- 不要只抛出RuntimeException异常,寻找一个合适的子类或者创建自己的异常类
- 不要只捕获Throwable异常,否则,这会使你的代码更难读,更难维护。
- 不要压制异常
- 在检查错误时,“苛刻”要比放任更好
- 在出错的时候抛出一个异常,要好于以后程序自己抛出异常
- 不要羞于传递异常
- 对于调用的方法中的异常,最好是继续传递这个异常,而不是自己捕获。
7.4 使用断言
7.4.1 断言的概念
断言(assert)机制:
允许在测试期间向代码中插入一些检查,而在生产代码中自动删除这些检查
7.4.2 启用和禁用断言
在默认情况下,断言是禁用的。可以在运行程序时用 -enableassertions 或 -ea 选项启用断言:
java -enableassertions MyApp
在某个类或整个包中启用断言
java -ea:MyClass
-ea com.mycompany.mylib MyApp
这条命令将为MyClass类以及com.mycompany.mylib包和它的子包中所有的类打开断言
7.4.3 使用断言完成参数检查
在Java中给出了三种处理系统错误的机制
- 抛出一个异常
- 日志
- 使用断言
什么时候使用断言:
- 断言失败是致命的,不可恢复的错误
- 断言检查只是在测试和开发阶段打开。
因此,不应该使用断言向程序其他部分通知发生了可恢复性的错误,
或者,不应该利用断言与程序用户沟通问题
断言只应该用于测试阶段确定程序内部错误的位置
7.5 日志
- 可以很容易地取消全部日志记录,或者仅仅取消某个级别以下的日志,而且可以很容易地再次打开日志开关
- 可以很简单地禁止日记录,因此,将这些日志代码留在程序中地开销很小
- 日志记录可以被定向到不同地处理器,如控制台显示,写至文件,等等
- 日志记录器和处理都可以对记录进行过滤.过滤器可以根据过滤器实现器指定的标准丢弃那些无用地记录项
- 日志记录可以采用不同地方式格式化,例如,纯文本或XML
- 应用程序可以使用多个日志记录器,他们使用与包名类似地有层次结构地名字
- 日志系统地配置有配置文件控制
7.5.1 基本日志
生成简单地的日志记录,可以使用全局日志记录器(global logger)并调用其info方法:
Logger.getGlobal().info("File->Open menu item selected");
在适当的地方取消所有的日志
Logger.getClobal.setLevel(Level.OFF)
7.5.2 高级日志
定义自己的日志记录器
可以调用getLogger方法创建或获取日志记录器
private static final Logger myLogger = Logger.getLogger("com.mycompany.myapp");
日志级别
- severe
- warning
- info
- config
- fine
- finer
- finest