1) 异常处理机制
在程序运行过程中,如果某条语句执行出现意外(比如,零除错误,IO错误等),那么Java的虚拟机就会停下,不会继续执行后续的语句,而是由里到外(因为一个方法内部,异常捕获是可以嵌套的),沿被调用关系寻找异常处理模块,如果:
A) 最终找到异常处理模块,则转而执行异常处理模块中的语句,异常处理语句执行完成后,则继续执行异常处理模块的后续语句(可能是finally模块),如下:
public class ExceptionTest { public static void main(String[] args) throws Exception { try { CallSomeMethods.DoA1(); } catch (ExceptionA e) { // TODO Auto-generated catch block HandlingSomething.PrintMsg(e.getMessage());//A2-7 } System.out.println("执行完毕!");//A1-7,A2-8 } } class ExceptionA extends Exception { public ExceptionA() { super("ExceptionA"); } public ExceptionA(String Msg) { super(Msg); } } class ExceptionA1 extends ExceptionA { public ExceptionA1() { super("ExceptionA1"); } } class ExceptionA2 extends ExceptionA { public ExceptionA2() { super("ExceptionA2"); } } class CallSomeMethods{ public static void DoA1() throws Exception { // 1 异常处理代码模块开始 try { // 2异常捕获代码块开始 try { //要测试哪个异常,请把产生该异常的语句放在前面 HandlingSomething.DoA3(); //A3-1 HandlingSomething.DoA2(); //A2-1 HandlingSomething.DoA1(); //A1-1 } catch (ExceptionA1 e) { HandlingSomething.PrintMsg(e.getMessage());//A1-2 } catch (ExceptionA e) { // 这里从新抛出只是为了演示finally块一定会被执行. // 继续抛出 HandlingSomething.PrintMsg("继续跑出2");//A2-2 throw new ExceptionA(e.getMessage()+":继续跑出2");//A2-3 } finally { HandlingSomething.PrintMsg("这里总会执行1");//A1-3,A2-4,A3-2 } // 2异常捕获代码块结束 // 2异常后续语句,如果前面的异常被处理而又没有抛出新的异常就会被执行。 System.out.println("这是异常捕获处理模块2的后续语句"); //A1-4 } catch (ExceptionA e) { // 继续抛出 HandlingSomething.PrintMsg("继续跑出1");//A2-5 throw new ExceptionA(e.getMessage()+":继续跑出1");//A2-6 } finally { HandlingSomething.PrintMsg("这里总会执行2");//A1-5,A2-7,A3-3 } // 1 异常处理代码模块结束 // 1异常后续语句,如果前面的异常被处理而又没有抛出新的异常就会被执行。 HandlingSomething.PrintMsg("这是异常捕获处理模块1的后续语句");//A1-6 } } class HandlingSomething { //这里为了演示,所以只简单的抛出异常 public static void DoA1() throws ExceptionA1 { throw new ExceptionA1(); } //这里为了演示,所以只简单的抛出异常 public static void DoA2() throws ExceptionA2 { throw new ExceptionA2(); } public static void DoA3() throws Exception { throw new Exception("这个异常不处理!"); } synchronized public static void PrintMsg(String Msg) { SimpleDateFormat theA =new SimpleDateFormat("yyyyMMddHHmmss"); System.out.println(Msg+":"+ theA.format(new java.util.Date())); } }
B)如果找不打异常处理块,最后会由Java虚拟机进行处理,是否会结束程序,要看具体的程序和具体的异常,上述测试DoA3时是会结束程序的。
Java和C#的异常处理机制也是典型的责任链模式。
2) 捕获方式
用try块将可能产生异常的代码包含起来,接着就是异常匹配处理块,异常匹配处理块可以有多个,注意:
异常匹配是按顺序进行的,找到匹配的异常处理块后,后面的匹配就会放弃匹配,因此在书写匹配块时,在异常类继承体系中,层次越低的放在前面,
层次越高的在后面。因为异常匹配不是精确匹配优先,而是匹配顺序优先,匹配只要满足“是”关系就可以了,也就是如果一个异常类是A,那么A及A的
所有子异常类都能在异常类A匹配的地方得到匹配。
3) 比较:
与C#相比,Java的异常处理机制更加严谨一些,它要求你对可能的异常A要么处理,要么显示的向调用者声明你没有处理异常A,否则Java的编译器就会报编译错误。
当然事情都是两面性的,在编译期就保证异常得到正确的处理当然是好事,但这种机制也会带来一些问题,一是如果调用关系复杂,异常比较多,代码看起来就不那么纯粹,
会稍显凌乱;二是对程序员的要求会比较高,特别是在写牵扯到事务的底层代码时,程序员必须非常谨慎的对待异常的处理,不能屏蔽上层调用的事务机制。除非必要,对于涉及到事务机制的代码中的异常应该尽量抛给调用者去处理。