- 论坛徽章:
- 0
|
摘自<高质量 Java 程序设计>
异常(Exception)处理
按照Java语言的定义,所谓异常处理指的就是向客户表示发生非正常情况的机制。在Java中有下列几种非正常情况:
编译错误(compile error):又称为语法错误,是因为错误地使用了语言。程序设计语言的基本目的之一,就是尽可能地在编译期间发现错误。同时Java又支持类的动态加载和多戊,这使得许多检查必须在运行时完成。异常是动行时检查的主角之一(另一个运行时检查的重要角色是断言)。
逻辑错误(logical error):又称为算法错误。无论是编译器,还是异常都不应该也不可能检查这类错误。只有程序员才能检查这种错误。
运行时错误(runtime error):程序在执行时所发生的执行错误。这是异常机制大展拳脚的地方。这类错误可能是数学运算发生溢出、磁盘空间不足或是文件损毁等在正常情况下不可能发生的错误。
当Java程序违反了Java的语义规则时,Java虚拟机会将发生的错误表示为一个异常。
所谓违反了Java的语义规则,包括两种情况。一种是Java类库内置的语义检查。例如数且下标越界,会引发IndexOutOfBoundsException;访问null的对象,会引发NullPointerException。
另一种情况就是java允许程序员扩展这种语义检查,也就是程序员可以创建自己的异常,并自由选择在何时用throw关键字引发异常。
任何异常都会引起程序控制流的转移,当异常被抛出(throw)后,Java虚拟机会在抛出异常的方法中寻找最近的匹配的catch语句,如果没有,则在调用方法中寻找,直至遍历调用栈中所有方法为止。如果没有找到任何匹配的catch语句,则会调用ThreadGroup.uncaughtException()方法。
所有的异常都是java.lang.Throwable的子类。更严谨一点地说,我们应当尽量使它们仅仅是Error、Exception或者RuntimeException的子类。
finally关键字是对Java异常处理模型的有益补充。finally块中的代码总会执行,而不管有无异常发生。使用finally可以维护对象的内部状态,并且finally是释放非内存资源的最佳场所。
Java异常机制的设计目的是提供一种可扩展的语义检查机制。
条款1 [规则]用时间频度来确定异常情况
条款2 [规则]用不需查异常来强化方法的约定
条款3 [规则]用Error的子类来表示系统级的异常和程序不必处理的异常
在Java的异常处理体系中有三种典型的异常:Error、Exception和RuntimeException。
系统级的异常是指一些绝对不应该发生的错误,如果这些错误发生了,在应用程序中进行恢复通常是不可能的。应用程序不应该也不必要声明和抛出Error的子类,也不必试图捕获Error的子类。最典型的系统级异常就是VirtualMachineError及其子类InternalError、OutOfManoryError、StackOverflowError和UnknownError。
程序不必处理的异常是指一些特殊用途的异常,例如J2SE1.4中增加的AssertionError。AssertionError在断言失败时被抛出。程序员不必捕获AssertionError,对待AssertionError的正确做法是检查代码并修改导致断言失败的错误。当然断言本身也有可能是错误的。从这个角度看来,AssertionError被系统用来执行一种动态检查策略,所表示的并非异常,而是程序中的错误,这种错误不应该在执行时处理,而应该在代码中消除这种错误。
条款2中不需查异常的用法与此处相似,不同的是AssertionError是由Java运行时系统处理的,而不需查异常则是由程序员手工建立的机制。在有些情况下,可以用断言代替不需查异常,前提是该不需查异常不会使对象处于不合法的状态。
条款4 [规则]用不从RuntimeException继承的Exception的子类来表示需查异常。
所谓需查异常(checked exception),是指在方法定义时用throws关键字声明的异常。
需查异常是最常见的异常用法。使用需查异常的第一个要点就是判断是否使用需查异常,判断规则参考条款1。
使用需查异常的第二个要点是不要将从RuntimeException继承得到的子类作为需查异常。RuntimeException的子类是专门为不需查异常保留的。
条款5 [规则]用RuntimeException的子类来表示不需查异常
在条款2中已经提到了不需查异常。所谓不需查异常,是指方法中抛出了该异常,但是在方法的定义中并未有用throws声明该异常。
对于不需查异常,编译器并不会强迫程序员使用catch来捕获该异常。
不需查异常通常都用RuntimeException的子类来表示,因为这类异常只有到了程序运行的时候才会发挥其作用。对程序员来说,RuntimeException的子类表明了程序的错误,因此得到不需查异常时,通常都要考虑修改你的代码了。条款2用不需查异常来强化方法的约定,违返方法调用的预定是最常见的程序错误之一。
不需查异常同需查异常一起,构成了异常的两大主流用法。将不需查异常限制为RuntimeException的子类,其它Exception的子类则用来表示需查异常,可以清晰地区分这两种用法。对于需查异常来说,客户必须用try-catch来捕获异常,否则编译时无法通过;对于不需查异常来说,客户必须正确地使用API,否则代码虽然可以通过编译,但是无法运行,不需查异常会立即使程序退出。
由于存在不需查异常,用throws声明直接抛出Exception类是不可接受的,这会强迫客户捕获Exception的所有子类,从而使不需查异常失效。从客户的角度来说,直接捕获Exception类也是不负责任的做法,这使得API提供的不需查异常完全失去了作用。
不需查异常同Error及其子类并无本质的区别,只不过Error及其子类通常由Java虚拟机使用。而不需查异常则由程序员使用。
java.lang.Object
java.lang.Throwable
java.lang.Exception
┕ BadLocationException, BadStringOperationException,
ClassNotFoundException, CloneNotSupportedException,
DataFormatException, IOException, SQLException,
java.lang.RuntimeException
┕ BufferOverflowException, ClassCastException, IllegalArgumentException,
IndexOutOfBoundsException, NullPointerException,
java.lang.Error
VirtualMachineError,
┕ InternalError, OutOfMemoryError, StackOverflowError, UnknownError
ThreadDeath, AssertionError, LinkageError, AWTError,
FactoryConfigurationError
条款6 [规则]用链式异常来保存原始异常信息
在捕获异常后,创建并抛出自定义的异常是常见的行为。但在抛出新的异常的同时,老的异常就被“淹没”掉了。如果不做任何特殊处理,新抛出的异常往往不能提供足够的信息。
J2SE1.4已经在Throwable类中封装了处理嵌套异常或是链式异常的功能。现在不是只有两个构造函数(其中一个无参数,另一个接受一条详细信息作为参数),而是有4个构造函数:
Throwable()
Throwable(String message)
Throwable(Throwable cause)
Throwable(String message, Throwable cause)
当创建自己的异常时,应该添加另外两个构造函数。那样,就可以在异常被创建时,很容易地传递产生该异常的原始异常。
另一种情况是丢失的异常。异常丢失的原因在于finally块中抛出了新的异常。如果finally块中有异常抛出,就会将原先的异常淹没掉。所以:不要在finally中抛出任何异常。 |
|