Thinking in Java 3 Edition 9:用异常来处理错误 Java的基本哲学是“糟糕的代码根本就得不到执行”。 捕捉错误的最佳时机应该是在编译的时候,也就是程序能运行之前。但 是,不是所有的错误都能在编译的时候被发现。有些问题只能到程序运行 的时候才能得到处理。它们要通过某种方式,让引发问题的代码将适当的 信息传给那些知道该怎样正确处理这些问题的程序。 C以及其它早期语言通常都有多个错误处理的方案,一般来说它们都是建 立在约定的基础上的,其本身并不属于语言。最常见的做法是,返回一个 特殊的值或设定一个标志,然后希望接收方会看到这个值或标记,并以此 判断是不是出了什么问题。但是,随着时间的推移,人们发现,那些使用 着类库的程序员们通常都认为自己是刀枪不入的——他们的心态可以归纳 为“错误,那时别人的事,我的代码绝不可能有错。”所以,也就不奇怪 他们为什么不检查错误条件了(况且有些错误也实在是蠢了点40,以至于 你都想不到还要去检查)。而且,如果你真的每次调用方法的时候,都作 遍完整的错误检査的话,那代码也就没法读了。由于程序员们还能用这 些语言凑出一些系统,因此他们一直拒绝承认这样一个事实:这种错误处 理的方法已经成为创建大型的、强壮的、可维护的程序的巨大障碍了。 解决的方案就是,强化错误处理的规范性屏弃其原有的随意性。实际上, 这种做法已经有很长的历史了。因为异常处理( exception handling)的 实现最早可以追溯到1960年代的操作系统,甚至是 BASIC的“on error goto”。不过C++的异常处理源于Ada,而Java的则主要建立 在C++的基础之上(尽管看上去更像 Object Pascal的)。 exception”这个词的意思有“我对此表示反对”的意味。问题发生的 时候,可能你不知道该如何处理,但是你知道不能再没心没肺的运行下去 了;你必须停下来,自然会有人知道该如何处理,而处理方案也应该藏在 什么地方。但是在现有的条件下,你还不知道问题的详细情况,因此没法 解决这个问题。所以你应该把问题交上去,那里会有人来作决定的(就像 是行政命令,一级管一级) 异常还有一个非常重要的好处,就是让错误处理代码显得更有条理。与原 先“程序要在多个地方检査同一个错误,并就地作处理”相比,现在,你 无需再在方法调用的时候作检査了(因为异常肯定能被捕获)。而且处理问 题的代码也集中到一个被称作“异常处理程序( exception handler)”地 方了。这种做法能为你省下不少代码,而且还把“讲述要作什么的代 码”,同“出错的时候该执行的代码”分开来了。总之,无论是写代码还 是调试程序,使用异常都比用老式的错误处理更有条理。 第1页共2页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com
Thinking in Java 3 rd Edition 1 ✁ ✂ 2 ✁ www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com 9: Java !"#$ %&'()*+ #,-.' !"/01-2345'%&( "6'789:;?@A#BC034 DEF GHIJK2LMNOPQ78R234%& C STU9VWXY<Z.-[\78@]#^_`a9:.b cdefg#UhijkXYZlmn#op^\ qrstue^\vw#xyz{|}@~R\stv#iS 34+##:01#K2 %&:<Z.:S #K¡¢#£ ¤'-,S#$ ¥¦ :§¨©ª («¬-2$® ¯ ✄☎ ✆ ✝ #S°k ±.²³;´§¨)µ¬#¶·±¸¹º@n"#.» ^¼½¾§¨¿#K $ ÀnÁ Âk%&:³'R 2XYÃ^2ÄÅ#Æ:^ÇȤÉRO^\¢ÊR?7 8@nËÌÍÎbÏÐÑ ÒÓÑ ÔÕ%&ÖÏ×Ø ÙÚ@] #ÒÛ78ÜÝÞßàUá-âÞãg# R?mnËÌ-äåæç ÆèZ78(exception handling) 1VSéê 1 960 ë ì»ÄÅ#í° BASIC on error goto= C+ + èZ78îk Ada#µ Java ïð;bc C+ + f)g(ñòg´óô Ob ject Pascal) excep ti on R\õâö-£÷øùú÷âû340ü "#'±LM¶ý78#+±LM'þÀÀ(´ ±`#x~-LM¶ý78#µ78@]$ @+1-©ª#±³LM34 «#ÆÀn ÙÚR\34,S±34 g´#K~-`»Úe( ô #^ò^ ) èZ³-^\Z;7# B78 ó-©8á %&;[\@§¨^\#i »78#1#± þ@nº"»§¨ (ÆèZe'/)µ¬783 4 $ !^\/"»èZ78%&(exception handler) @ R?mn'±#$ #µ¬³%&;» #" '(` ))#*+ ³ º,%&#èZ.-A78ó-©8
hapter 9: Error Handl 由于“异常处理”是Java唯一的正式报告错误的方式,而且Java编译 器还对此作了强制要求,因此即使不学异常处理,也可以写出很多本书中 的例程。本章会介绍如何正确地处理异常以及,如何定义你自己的异常 以供方法出问题的时候使用。 基本异常 “异常条件( exceptional/ condition)”是一种能阻止正在运行的方法或 其某一部分继续运行下去的问题。把异常条件同普通问题区分开来,这点 很重要。遇到普通问题的时候,你在当前的运行环境下有足够的信息来处 理这个困难。对于异常条件,由于你得不到足够的用以处理这个问题的信 息,因此不能在“当前的运行环境下( in the current context)”继续运 行下去。你只能跳出当前的运行环境,并且把问题交到上层的运行环境。 这就是抛出异常的时所发生的事情。 除法就是一个例子。如果知道会除以零的话,还是应该去测试一下的。但 是被除数是零又代表什么呢?或许你知道。你可以根据问题的上下文,决 定如何处理零作分母的情况。但是如果这不是一个预料中的值,因此你不 知道该如何处理的话,那就必须抛出一个异常,而不是顺着执行的路径继 续下去了。 当你抛出异常的时候,有几件事会随之发生。首先要像创建其它Java对 象那样,创建一个异常对象:在堆里,用new。然后停下当前的执行路 径(这条路是不能再继续下去了),再将异常对象的 reference从当前运行 环境当中弹岀去。现在异常处理机制开始接管程序了,它会去找到一个合 适的地方来继续执行这个程序。这个地方就是“异常处理程序 ( exception handler)”,其功能就是将程序从问题中恢复过来,于是程 序或者是尝试去换一条运行路径,或者是继续运行下去。 举一个抛异常的简单例子,看看t这个 reference。它可能还没有经过初 始化,因此你应该先检查一下再用它去调用方法。你可以创建一个表示错 误信息的对象,并把这个对象“抛出”当前的运行环境,这样就能把出错 的信息传递到更高层的运行环境中了。这被称为“抛出一个异常 ( throwing an exception)。”就像这里看到的: if(t throw new NullPointerException () 这样就抛出了异常,于是你就——一在当前运行环境下——一无需再为这个问 题操心了。自然会有人来处理的。我们这就把他介绍给你 异常的参数 第2页共3页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com
Chapter 9: Error Handling with Exceptions www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com ✞ 2 ✟ ✠ 3 ✟ ÂkèZ78 Java .^PA/0@A#µ¬ Java ! 1³÷» Ò2;3#Æ4èZ78#$S+ä[5! 6%7~89¶ýPQ78èZST#¶ýe:±èZ# S;@n34" èZ©ª(exceptional condition)^?'^>'?@(´34èZ©ªA<34B'(`#R¯ ä;CA<34"#±F*(DE-FGGH`7 8R\HI÷kèZ©ª#Âk±FGS78R\34G H#Æ'F*(DE(in the current context)?@( ´±5'JF*(DE#i¬34 gK(DE R LèZ,0ü¢ Mn ^\6N¶·LM~MSO¿#³´P,^+ /MQOR øST tU±LM±SV34gW#Ú e¶ý78O»'X«+¶·R^\YZ!s#Ʊ LM¶ý78¿#K L^\èZ#µ[\]? @´ F±LèZ"#-^ª¢~)0ü_;ôÎbU9 Java ÷ `KO#Îb^\èZ÷`Êa# newxyF*\ ](R©\'þ?@´ )#þDèZ÷` referen ce bF*( DEF!c´1èZ782(d|ò%& #9~´e^\f E@`?@R\%&R\@ èZ78%& (exception handler)#Ug' D%&b34!hi=`#k% &tjk,´l^©(\]#tj?@(´ m^\LèZno6N# t R\ referen ce9'³À-Ì=p dÛ#Ʊ§¨^þ9´º@n±SÎb^\øù GH÷`#iR\÷`LF*(DE#RO ' GHIqórK(DE! R/"L^\èZ (throwing an exception) ôRÊ if(t == null) throw new NullPointerException(); RO L èZ#k± F*(DEþR\3 4ì x~-`78£:R 89J±
Thinking in Java 3 Edition 就像Java中的其它对象,你也可以用new在堆中创建异常,而new 会调用它的构造函数并为它分配内存。所有的标准异常都有两个构造函 数;第一个是默认的构造函数,第二个是要拿一个字符串当参数的,因此 你可以在异常中放入一些相关的信息: throw new NullPointerException("t=null") 就像你将看到的,将来这个字符串可以用各种方法提取出来。 关键词 throw会引发许多很神奇的事情。通常情况下,你会先用new 来创建一个表示错误条件的对象。然后再把这个对象的 reference交给 throw。虽然这个对象不是方法设计要返回的那种对象,但实际上,方 法还是返回这个对象。有一种理解异常处理的简化思路,就是把它想成一 种不同的返回机制。但是这种想法别走得太远,否则就有麻烦了。你也可 以用“抛异常”的方法从方法的作用域里退出。总之,它会返回了一个 值,并且退出了方法或作用域 异常处理与“从方法中正常返回”的相同点就到此为止了,因为它所返回 的地点同正常的方法调用所返回的地点是完全不同的。(它最终是要在某 个异常处理程序里面得到解决的,而这个程序可能离异常发生的地方很 远——与“调用栈( call stack)”隔着很多层。) 此外,你还可以抛出任何 Throwable对象(这是异常的根类)。通常情 况下,你得根据不同的错误抛出不同的异常。错误信息由保存在异常对象 中信息,以及异常类的名字表示。于是上层的运行环境就可以用这个异常 来决定该怎么干了。(通常,异常类型的名字是唯一的信息,异常对象不 会保存什么有用的东西。) 捕捉异常 如果方法抛出了异常,那么必须要有能“捕捉”这个异常,并且处理这个 异常的程序。异常处理有一个好处,就是它能让你集中精力在一个地方解 决问题,然后将处理错误的代码分开来放在另一个地方。 要想理解异常是怎样被捕捉到的,你必须首先懂得“守护区域( guarded reglon)”的概念。这是一段可能会产生异常的代码,并且后面还跟着要 处理这些异常的代码。 Try区块 如果你从方法里面抛出了一个异常(或是在这个方法调用的另一个方法里 面抛出一个异常),那么抛出异常的同时,这个方法会退出运行。如果你 不想被 throw出方法,那么你可以在这个方法的内部建一个特殊的区块 第3页共3页 www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com
Thinking in Java 3 rd Edition ✞ 3 ✟ ✠ 3 ✟ www.wgqqh.com/shhgs/tij.html email:shhgs@sohu.com ô Java !U9÷`#±$S new a!ÎbèZ#µ new ~º9stuQi9'vwx,-vyèZ.-z\stu Q {^\|stuQ#{}\;~^\FQ#Æ ±SèZ!^2GHÊ throw new NullPointerException("t = null"); ô±D#D`R\S ?@n` õ th row ~C0U[䥢 \èZ78%&ÙÚ#µR\%&'èZ0ü@ä º(cal l stack)ä[K) #±³SLý Th rowable ÷`(RèZ)b^\qrB«