▲ 处理错误 ▲ 日志
▲ 捕获异常 ▲ 调试技巧
▲ 使用异常机制的技巧 ▲ GUI程序排错技巧
▲ 使用断言 ▲ 使用调试器
如果一个用户在运行程序期间,由于程序的错误或一些外部环境的影响造成用户数据的丢失,用户就有可能不再使用这个程序了。为了避免这类事情的发生,至少应该做到以下几点:
- 向用户通告错误;
- 保存所有的工作结果;
- 允许用户已妥善的形式退出程序。
对于异常情况,Java使用一种称为异常处理(exception handing)的错误捕获机制处理。
11.1 处理错误
如果由于出现错误而使得某些错做没有完成,程序应该:
- 返回到一种安全状态,并能够让用户执行一些其他的命令。
- 允许用户保存所有操作的结果,并以一种适当的方式终止程序。
11.1.1 异常分类
在Java程序设计语言中,异常对象都是派生于Throwable类的一个实例。
图1 Java中的异常层次结构
所有的异常都是由Throwable继承而来,但在下一层立即分解为两个分支:Error和
Exception。
Error类层次结构描述了Java运行时系统内部错误和资源耗尽错误。
Exception层次结构划分为两个分支:由程序错误导致的异常属于RuntimeException;而程序本身没有问题,但由于像I/O错误这类问题导致的异常属于其他异常。
Java语言规范将派生于Error类或RuntimeException类的所有异常称为未检查(unchecked)异常,将所有其他的异常称为已检查(checked)异常。
11.1.2 声明已检查异常
如果遇到无法处理的情况,那么Java的方法可以抛出一个异常。
方法应该在其首部声明所有可能抛出的异常。
遇到下面4中情况应该抛出异常:
- 调用一个抛出已检查异常的方法。
- 程序运行过程中发现错误,并利用throw语句抛出一个已检查异常。
- 程序错误,例如啊a[-1]=0会抛出一个ArrayIndexOutOfBoundsException这样的未检查异常。
- Java虚拟机和运行时库出现内部错误。
对于那些可能被他人使用的Java方法,应该根据异常规范,在方法的首部声明这个方法可能抛出的异常。
11.1.3 如何抛出异常
对一个已存在的异常类,将其抛出方法:
1)找到一个合适的异常类。
2)创建这个类的一个对象。
3)将对象抛出。
11.1.4 创建异常类
定义一个派生于Exception的类,或者派生于EXception子类的类。
定义的类应该包含两个构造器,一个是默认构造器;另一个是带有详细描述信息的构造器。
11.2 捕获异常
想要捕获异常,必须设置try/catch语句块。
如果在try语句块中的任何代码抛出了一个在catch子句中说明的异常类,那么
1)程序将跳过try语句块的其余代码。
2)程序将执行catch子句中的处理器代码。
如果在try语句块中的代码没有抛出任何异常,那么程序将跳过catch子句。
如果方法中的任何代码抛出了一个在catch子句中没有声明的异常类型,那么这个方法就会立刻退出。
通常,应该捕获那些知道如何处理的异常,而将那些不知道怎样处理的异常继续进行传递。
11.2.1 捕获多个异常
在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。可以按照下列方式为每个异常类型使用一个单独的catch子句:
异常对象可能包含与异常本身有关的信息。想要获得对象的更多信息,可以试着使用e.getMessage()得到详细的错误信息,或者使用e.getClass().getName()的到异常对象的实际类型。
在Java SE 7 中,同一个catch子句可以捕获多个异常类型。如果对应异常的动作是一样的,就可以合并catch子句:
只有当捕获的异常类型彼此之间不存在子类关系时才需要这个特性。
11.2.2 再次抛出异常与异常链
在catch子句中可以抛出一个异常,这样可以让用户抛出子系统中的高级异常,而不会丢失原始异常的细节。
没弄明白是什么意思,只知道可以记录一个异常,然后再把它重新抛出,而不做任何改变。
11.2.3 finally子句
不管异常是否被捕获,finally子句中的代码都被执行。
1)代码没有抛出异常。程序首先执行try语句块中的全部代码,然后执行finally子句中的代码。随后执行try语句块之后的第一条语句。
2)抛出一个在catch子句中捕获的异常。程序首先执行try语句块中的所有代码,直到发生异常为止。此时,将跳过try语句块中的剩余代码,转去执行与该异常匹配的catch子句中的代码,最后执行finally子句中的代码。
3)代码抛出了一个异常,但这个异常不是由catch子句捕获的。程序首先执行try语句块中的所有代码,直到异常被抛出为止。此时,将跳过try语句块中的剩余代码,转去执行finally子句中的语句,并将异常抛给这个方法的调用者。
try语句可以只有finally子句,而没有catch子句。
11.2.4 带资源的try语句
假如资源属于一个实现了AutoCloseable接口的类,Java SE 7 为这种代码模式提供了一个很有用的快捷方式,AutoCloseable接口有一个方法:
void close() throws Exception
try语句块退出时,会自动调用res.close()。
11.2.5 分析堆栈跟踪元素
堆栈跟踪是一个方法调用过程的列表,它包含了程序执行过程中方法调用的特定位置。
可以调用Throwable类的printStackTrace方法访问堆栈跟踪的文本描述信息。
11.3 使用异常机制的技巧
- 异常处理不能代替简单的测试,只在异常情况下使用异常机制。
- 不要过分地细化异常
- 利用异常层次结构
- 不要压抑异常
- 在检查错误时,苛刻要比放任更好
- 不要羞于传递异常
11.4 使用断言
断言机制允许在测试期间向代码中插入一些检查语句,当代码发布时,这些插入的检测语句将会被移走。
断言格式:
- assert 条件;
- assert 条件:表达式;
这两种形式都会对条件进行检测,如果结果为false,则抛出一个AssertionError异常。第二种形式中,表达式将被传入AssertionError的构造器,并转行成一个消息字符串。
11.4.1 启用和禁用断言
在默认情况下,断言被禁用。
可以在运行程序时使用-enableassertions或-ea选项启用它:java-enableassertions MyApp
启用或禁用断言时不必重新编译程序。
开发工具中启用断言:
1.在eclipse中,Run-> Run Configurations -> Arguments页签 -> VMarguments文本框中加上断言开启的标志:-enableassertions 或者-ea
2.在myEclipse中,Windows-> Preferences ->Java ->Installed JREs ->点击正使用的JDK ->Edit->Default VM Arguments文本框中输入:-ea
11.4.2 使用断言完成参数检查
选择断言的条件:
- 断言失败是致命的、不可恢复的错误。
- 断言检查只用于开发和测试阶段。
不应该使用断言向程序的其他部分通告发生了可恢复的错误,或者,不应该作为程序向用户通告问题的手段。
断言只应该用于在测试阶段确定程序内部的错误位置。
11.5 记录日志
记录日志API就是为了帮助程序员定位问题而设计的。这些API的优点有:
- 可以很容易地取消全部日志记录,或者仅仅取消某个级别的日志,而且打开和关闭这个操作也很容易。
- 可以简单地禁止日志记录和输出,因此,这些日志代码留在程序中的开销很小。
- 日志记录可以被定向到不同的处理器,用于在控制台中显示,用于存储在文件中等。
- 日志记录器和处理器都可以对记录进行过滤。过滤器可以根据过滤实现器制定的标准丢弃那些无用的记录。
- 日志记录可以采用不同的方式可视化,例如村文本或XML
- 应用程序可以采用多个日志记录器,它们使用类似包名的这种具有层次结构的名字。
- 在默认情况下,日志系统的配置由配置文件控制。如果需要的话,应用程序可以替换这个配置。
Java核心技术介绍的日志应该是Java自带的日志系统,JDBC中好像重新封装了日志功能,到时候再来比较。