\"异常\"指的是程序运行时出现的非正常情况。在用传统的语言编程时,程序员只能通过函数的返回值来发出错误信息。这易于导致很多错误,因为在很多情况下需要知道错误产生的内部细节。通常,用全局变量errno来存储\"异常\"的类型。这容易导致误用,因为一个errno的值有可能在被处理?reg;前被另外的错误覆盖掉。即使最优美的C语言程序,为了处理\"异常\"情况,也常求助于goto语句。Java对\"异常\"的处理是面向对象的。一个Java的Exception是一个描述\"异常\"情况的对象。当出现\"异常\"情况时,一个Exception对象就产生了,并放到产生这个\"异常\"的成员函数里。
8.1基础
Java的\"异常\"处理是通过5个关键词来实现的:try,catch,throw,throws和finally。用try来执行一段程序,如果出现\"异常\",系统抛出(throws?copy;一个\"异常\",你可以通过它的类型来捕捉(catch?copy;它,或最后(finally?copy;由缺省处理器来处理。下面是\"异常\"处理程序的基本形式:try{//程序块}catch(ExceptionType1e){//对ExceptionType1的处理}catch(ExceptionType2e){//对ExceptionType2的处理throw(e);//再抛出这个\"异常\"}finally{}
8.2\"异常\"的类型
在\"异常\"类层次的最上层有一个单独的类叫做Throwable。这个类用来表示所有的\"异常\"情况。每个\"异常\"类型都是Throwable的子类。Throwable有两个直接的子类。一类是Exception,是用户程序能够捕捉到的\"异常\"情况。我们将通过产生它的子类来创建自己的\"异常\"。另一类是Error,它定义了那?copy;通常无法捕捉到的\"异常\"。要谨慎使用Error子类,因为它们通常会导致灾难性的失败。在Exception中有一个子类RuntimeException,它是程序运行时自动地对某?copy;错误作出反应而产生的。
8.3不捕捉\"异常\"
\"异常\"对象是Java在运行时对某?copy;\"异常\"情况作出反应而产生的。例如,下面这个小程序包含一个整数被0除的\"异常\"。
classExc0{publicstaticvoidmain(Stringargs){intd=0;inta=42/d;}}
当Java执行这个除法时,由于分母是0,就会构造一个\"异常\"对象来使程序停下来并处理这个错误情况,在运行时\"抛出\"(throw?copy;这个\"异常\"。说\"抛出\"是因为它象一个滚烫的马铃薯,你必须把它抓住并立即处理。程序流将会在除号操作符处被打断,然后检查当前的调用堆栈来查找\"异常\"。一个\"异常\"处理器是用来立即处理\"异常\"情况的。在这个例子里,我们没有编一个\"异常\"处理器,所以缺省的处理器就发挥作用了。缺省的处理器打印Exception的字符?reg;值和发生\"异常\"的地点。
下面是我们的小例子的输出。
C:\\>javaExc0java.lang.arithmeticException:/byzeroatExc0.main(Exc0.java:4)
8.4try与catch
通常我们希望自己来处理\"异常\"并继续运行。可以用try来指定一块预防所有\"异常\"的的程序。紧跟在try程序后面,应包含一个catch子句来指定你想要捕捉的\"异常\"的类型。例如,下面的例子是在前面的例子的基础上构造的,但它包含一个try程序块和一个catch子句。classexc1{publicstaticvoidmain(stringargs){try{intd=0;inta=42/d;}catch(arithmeticexceptione){system.out.println(\"divisionbyzero\");}}}
catch子句的目标是解决\"异常\"情况,把变量设到合理的状态,并象没有出错一样继续运行。如果一个子程序不处理某个\"异常\",则返到上一级处理,直到最外一级。
8.5多个catch子句
在某情况下,同一段程序可能产生不止一种\"异常\"情况。你可以放置多个catch子句,其中每一种\"异常\"类型都将被检查,第一个与?reg;匹配的就会被执行。如果一个类和其子类都有的话,应把子类放在前面,否则将永远不会到达子类。下面是一个有两个catch子句的程序的例子。
classMultiCatch{publicstaticvoidmain(Stringargs){try{inta
=args.length;System.out.println(\"a=\"+a);intb=42/a;intc=
{1};c[42]=99;}catch(ArithmeticExceptione){System.out.println(\"div
by0:\"+e);}catch(ArrayIndexOutOfBoundsExceptione)
{system.out.println(\"arrayindexoob:\"+e);}}}
如果在程序运行时不跟参数,将会引起一个0做除数的\"异常\",因为a的值为0。如果我们提?copy;一个命令行参数,将不会产生这个\"异常\",因为a的值大于0。但会引起一个ArrayIndexOutOfBoundexception的\"异常\",因为整型数组c的长度是1,却给c[42]赋值。下面是以上两种情况的运行结果。
C:\\>javaMultiCatcha=0divby0:java.lang.arithmeticexception:/by
zeroC:\\>javaMutiCatch1a=1arrayindexoob:
java.lang.ArrayIndexOutOfBoundsException:42
8.6try语句的嵌套
你可以在一个成员函数调用的外面写一个try语句,在这个成员函数内部,写另一个try语句保护其他代码。每当遇到一个try语句,\"异常\"的框架就放到堆栈上面,直到所有的try语句都完成。如果下一级的try语句没有对某种\"异常\"进行处理,堆栈就会展开,直到遇到有处理这种\"异常\"的try语句。下面是一个try语句嵌套的例子。
classMultiNest{staticvoidprocedure{try{intc={1}:c[42]
=99;}catch(ArrayIndexOutOfBoundsexceptione)
{System.out.println(\"arrayindexoob:\"+e);}}publicstaticvoid
main(Stringargs){try{inta=args.length;system.out.println(\"a
=\"+a);intb=42/a;procedure;}catch(arithmeticExceptione)
{System.out.println(\"divby0:\"+e);}}}
成员函数procedure里有自己的try/catch控制,所以main不用去处理ArrayIndexOutOfBoundsException。
8.7throw语句
throw语句用来明确地抛出一个\"异常\"。首先,你必须得到一个Throwable的实例的控制柄,通过参数传到catch子句,或者用new操作符来创建一个。下面是throw语句的通常形式。
throwThrowableInstance;
程序会在throw语句后立即终止,它后面的语句执行不到,然后在包含它的所有try块中从里向外寻找含有与其匹配的catch子句的try块。下面是一个含有throw语句的例子。
classThrowDemo{staticvoiddemoproc{try{thrownewNullPointerException(\"de3mo\");}catch(NullPointerExceptione){System.out.println(\"caughtinsidedemoproc\");throwe;}}publicstaticvoidmain(Stringargs){try{demoproc;}
catch(NullPointerExceptione){system.out.println(\"recaught:\"+e);}}}
8.8throws语句
throws用来标明一个成员函数可能抛出的各种\"异常\"。对大多数Exception子类来说,Java编译器会强迫你声明在一个成员函数中抛出的\"异常\"的类型。如果\"异常\"的类型是Error或RuntimeException,或它们的子类,这个规则不起作用,因为这?copy;在程序的正常部分中是不期待出现的。如果你想明确地抛出一个RuntimeException,你必须用throws语句来声明它的类型。这就重新定义了成员函数
的定义语法:typemethod-name(arg-list)throwsexception-list{}
下面是一段程序,它抛出了一个\"异常\",但既没有捕捉它,也没有用throws来声明。这在编译时将不会通过。
classThrowsDemo1{staticvoidprocedure[System.out.println(\"inside
procedure\");thrownewIllegalAccessException(\"demo\");}publicstatic
voidmain(Stringargs){procedure;}}
为了让这个例子编译过去,我们需要声明成员函数procedure抛出了IllegalAccessException,并且在调用它的成员函数main里捕捉它。下面是正确的例子:
classThrowsDemo{staticvoidprocedurethrowsIllegalAccessException
{System.out.println(\"insideprocedure\");thrownew
IllegalAccessException(\"demo\");}publicstaticvoidmain(Stringargs)
{try{procedure;}catch(IllegalAccessExceptione)
{System.out.println(\"caught\"+e);}}}
下面是输出结果:
C:\\>javaThrowsDemoinsideprocedurecaught
java.lang.IllegalAccessException:demo
8.9finally
当一个\"异常\"被抛出时,程序的执行就不再是线性的,跳过某?copy;行,甚至会由于没有与?reg;匹配的catch子句而过早地返回。有时确保一段代码不管发生什么\"异常\"都被执行到是必要的,关键词finally就是用来标识这样一段代码的。即使你没有catch子句,finally程序块也会在执行try程序块后的程序?reg;前执行。每个try语句都需要至少一个与?reg;相配的catch子句或finally子句。一个成员函数返回到调用它的成员函数,或者通过一个没捕捉到的\"异常\",或者通过一个明确的return语句,finally子句总是恰好在成员函数返回前执行。下面是一个例子,它有几个成员函数,每个成员函数用不同的途径退出,但执行了finally子句。
classFinallyDemo{staticvoidprocA{try
{System.out.println(\"insideprocA\");thrownew
RuntimeException(\"demo\");}finally{System.out.println(\"procA\'s
finally\");}}staticvoidprocB{try{System.out.println(\"inside
procB\");return;}finally{System.out.println(\"procB\'sfinally\");}}
publicstaticvoidmain(Stringargs){try{procA;}catch(Exception
e);procB;}}
下面是这个例子的运行结果:
C:\\>javaFinallyDemoinsideprocAprocA\'sfinallyinsideprocBprocB\'sfinally
本章小结
1.\"异常\"指的是程序运行时出现的非正常情况。2.在\"异常\"类层次的最上层的类叫Throwable,它有两个直接的子类:Exception和Error。3.Java的\"异常\"处理通过5个关键词来实现:try,catch,throw,throws和finally。