Java 异常处理学习总结
Java 异常处理学习总结 ---------------------------------------------------------------------------------------
1. 语言提供内建一致的错误处理机制,避免不一致的错误处理方式和风格。其基本思想是,让错误源将合适的信息传到某个接收者进行处理;这个接收者可能与错误源位于同一抽象层次,更可能位于更高的抽象层次。做个简单的类比,当员工无法处理某些问题时,就要提交到高层管理去处理。
2. Java 的异常处理语法并不多, try-catch-finally, throw, throws ,关键在于异常的合理使用: 何时使用异常;如何正确使用异常;当异常发生时,要确保程序处于正确稳定的状态下。You should always ask : when exception happens , will everything properly cleanup ?
3. 异常处理机制的益处: 将正确情形代码与错误处理相分离。
遵循传统错误处理方式的代码是: 使用异常处理的代码是:
if1 (! Exception1) { try {
// 正确情形代码1 // 正确情形代码1 ……
} // 正确情形代码N
else1 { } catch (Exception1) {
// 错误情形代码1 // 错误情形代码1
} }
…… ……
ifN (! ExceptionN) { } catch (ExceptionN) {
// 正确情形代码N // 错误情形代码N
} } finally {
elseN { // 清理资源,或使程序回复某个状态
// 错误情形代码N }
}
4. 自定义异常的方法很简单,让它继承自Java 中的已有异常,比如 Exception . 自定义异常的一个重要事项是异常类的名字必须取好,望文生义,具有自描述性。因为,错误源的信息提示是非常重要的。
5. 异常的重要继承体系: RuntimeException ---> Exception --- > Throwable , Error ---> Throwable . 其它异常均是继承于此。异常的重要方法有: getClass().getName() [获取异常类名];getMessage() , getLocalizedMessage() [异常信息]; new SomeException(String), new SomeException(Throwable)[构造器]; fillInStackTrace(), getCause() [用于重抛异常];initCause(Throwable) [异常链] ;printStackTrace() , printStackTrace(PrintStream) , printStackTrace(PrintWriter) [打印异常发生栈调用信息] 。
6. Java 异常链: 将刚刚捕获的异常对象作为构造器参数传入将要抛出的异常对象,或者作为 initCause() 方法的参数传入,可以使即将抛出的异常保存有刚刚捕获的异常对象的信息,构成异常链,用来表达因果关系。
7. 若类的方法 f 中抛出异常A1,A2,...,An,则该方法的声明必须指定异常声明 f() throws A1, A2, ..., An (A1, A2, ..., An 若是 RuntimeException 或其派生类异常,则可以不指定。)
8. 异常占位或预留技术: 方法中并不抛出异常,但方法中仍然指定该异常声明 。这样做的好处时,当真正需要在方法中抛出该异常时,不会影响客户代码;因为客户代码已经对该异常进行处理了(在没有实际抛出该异常之前)。
9. 异常声明限制:
(1) 子类构造器的异常声明列表必须是基类构造器异常声明列表的超集,即:若基类构造器的异常声明列表是 {A1,A2,...,An} , 则子类构造器的异常声明列表 S >= {A1,A2,...An} ; 这是因为,调用子类构造器之前必然调用基类构造器,因此,子类构造器必须处理基类构造器所声明的所有异常;
(2) 子类覆写方法的异常声明列表必须是基类方法异常声明列表的子集,即:若基类方法的异常声明列表是 {A1,A2,...,An} , 则子类覆写方法的异常声明列表 S <= {A1,A2,...An} ; 这是因为, 客户代码是基于基类方法提供的公共接口来编程的,只应当处理基类方法提供的公共接口所声明的异常。
(3) 子类型的接口实现方法的异常声明列表必须是接口方法异常声明列表的子集。这是因为,客户代码是基于接口方法的声明来编程的,只应当处理接口方法提供的异常说明。
10. RuntimeException 及其派生类的特殊性:
(1) 若异常发生,则系统会自动抛出RuntimeException 或其派生类对象,无需程序员显式抛出;
(2) 若方法中抛出 RuntimeException 或其派生类异常,则方法的异常说明中无需指定这些异常;
(3) 由于 RuntimeException 及其派生类无需程序员显式抛出,因此,编译器不会给予 try-catch-finally 提示; 程序员可能不会去捕获这样的异常,从而使这些RuntimeException 通过重重关卡逃出 Main() 而未被捕获, 此时程序将自动调用该异常的printStackTrace,并终止程序;
(4) RuntimeException 及其派生异常通常代表程序中的错误,比如 数组的 IndexOutOfBoundsException 异常;应尽量避免出现此类异常。
11. 在构造器中抛出异常会导致非常棘手的问题;因此,尽量不要在构造器中抛出任何异常;尽量使构造器能够 100%的成功运行。
12. 异常匹配: 当异常发生时,系统将使用new创建一个异常对象(像创建一个普通Java对象一样),并获得一个引用;接着,系统将中止当前执行路径,而转到相应的异常处理程序中(类似中断处理,但不会返回到原执行路径); 匹配哪个异常处理程序按以下规则:
(1) 通常从紧随其后的一系列 catch 语句进行搜索;搜索顺序是按catch语句出现的先后顺序;一旦匹配成功,就不再匹配后面的catch语句了;
(2) 匹配的准则是: catch (B) 将捕获一切 B 及其派生类对象 (遵循多态机制) ;反之, 若抛出 A, 则可以由 A 的一切父类对象来捕获。先来先得。
(3) 异常屏蔽: 若抛出B对象,且 B ---> B1 ---> B2 ---> ... ---> Bn, --->表示继承关系,则在该继承层次上,越靠近B(更精准匹配),其catch 语句的顺序越应放在前面(如果同时出现的话),即 catch 语句块的顺序应该依次是 catch(B1),catch(B2), ..., catch(Bn)。比如 RuntimeException 应置于 Exception 之前,因为 Exception 可以捕获一切异常,这就是说,若顺序是:... catch(Exception e ) { // } catch (RuntimeException e) { } , RuntimeException 将没有执行的机会。
13. 异常使用准则: 避免立即捕获和处理异常,而是将它交由合适的层次进行处理。因为错误处理的抉择可能涉及到系统整体的设计,而不是局部的问题。这类似于公司的客户投诉中心:问题通常不一定是由最底层人员的接收者来处理,而是一层层提交给更合适的某个管理层进行处理。
14. 受检异常及其处理方法: 通俗地说, 非 RuntimeException 异常都是受检异常,它在编译时被检查出来,必须立即进行处理:要么 提交给更高层,要么立即在紧随其后的catch 语句进行处理。 受检异常带来的麻烦是,有时,你并没有足够的信息来进行决策和处理(必须提交到更高层),或者根本不想进行处理(试想你调用某个人的方法,该方法抛出异常,而你会觉得这似乎不关自己的事情)。
受检异常有两种处理方法: 传递到控制台,main() throws XXXException ; 或者将其包装成 RuntimeException 。采用后者,你并不会丢弃异常(not to be silently swallowed),方法中也不必指定异常说明,并且,产生的异常仍然被保留下来。但在某个地方,你仍然需要去处理它。
15. 错误处理的思考: 最理想的错误处理应该是怎样的呢? 程序中应织有一张恢恢疏而不露的天网,程序中的所有错误都能够被捕获,并在合适的地方得到处理。结合 Java 的异常处理机制: A. 在可能出现错误的地方抛出异常,避免立即进行处理(除非有足够信息能够确知如何进行处理); B. 当捕获到异常时,如果确知如何处理,用catch块进行处理;否则,应该将其封装并重新抛出,提交给更高层进行处理。
16. 《Thinking In Java》: 语言可以提供一些好的特性供程序员运用,但最终的使用权在程序员手上。 A good language should help programmers program well , but no languge could prevent programmers from bad practice. 学习一门语言,不仅仅是掌握其语法和用法,更要领悟其设计思想,避开语言设计的不足和陷阱,使用语言的优良特性编写可靠、可维护的系统。