Error Handling with Exceptions【2】
2007-11-16 08:57
197 查看
Thekeywordthrowcausesanumberofrelativelymagicalthingstohappen.Typically,you’llfirstusenewtocreateanobjectthatrepresentstheerrorcondition.Yougivetheresultingreferencetothrow.Theobjectis,ineffect,“returned”fromthemethod,eventhoughthatobjecttypeisn’tnormallywhatthemethodisdesignedtoreturn.Asimplisticwaytothinkaboutexceptionhandlingisasadifferentkindofreturnmechanism,althoughyougetintotroubleifyoutakethatanalogytoofar.Youcanalsoexitfromordinaryscopesbythrowinganexception.Butavalueisreturned,andthemethodorscopeexits.
Throw关键词引发了一系列相关的事情,典型的,你首先会用new方法创建一个对象来描述异常的条件,将这个对象给了throw方法。这个方法会返回这个对象,尽管它的类型不是这个方法设计所返回的类型。一种简单的方式来看“异常处理程序”就是一种不同返回类型的机制,但是这种机制不要走的太远否则就麻烦了,你也可以在一段顺序执行的区域采用throw一个异常来退出程序。但是返回结果,退出了方法或者作用域。
Anysimilaritytoanordinaryreturnfromamethodendshere,becausewhereyoureturnissomeplacecompletelydifferentfromwhereyoureturnforanormalmethodcall.(Youendupinanappropriateexceptionhandlerthatmightbefar—manylevelsawayonthecallstack—fromwheretheexceptionwasthrown.)
异常处理和正常程序的返回相同点就是这些了。因为你抛出异常的地方和你返回正常发发调用的地方有着本质的区别。你解决这个异常信息的地方可能距离异常的抛出地方很远。
Inaddition,youcanthrowanytypeofThrowable(theexceptionrootclass)objectthatyouwant.Typically,you’llthrowadifferentclassofexceptionforeachdifferenttypeoferror.Theinformationabouttheerrorisrepresentedbothinsidetheexceptionobjectandimplicitlyinthenameoftheexceptionclass,sosomeoneinthebiggercontextcanfigureoutwhattodowithyourexception.(Often,theonlyinformationisthetypeofexception,andnothingmeaningfulisstoredwithintheexceptionobject.)
另外,你可以抛出任何类型的以Throwable为基类的对象。典型的,你会根据不同的错误抛出不同类型的异常信息。关于错误的信息的描述在对象里并且暗含在类的名字当中,所以在更高的环境中可以识别出根据异常信息如何处理。(通常,有用的信息就是这个对象的类型,而在对象中存储的信息没有什么意义)
如果一个方法抛出了一个异常,它必须确保这个异常信息可以被捕获和处理掉。“异常处理程序”的优势之一就是它允许你在一个地方集中精力解决这个问题,而到另外一个地方来解决这个异常。
Toseehowanexceptioniscaught,youmustfirstunderstandtheconceptofaguardedregion.Thisisasectionofcodethatmightproduceexceptionsandisfollowedbythecodetohandlethoseexceptions.
要想知道如何捕获异常,你必须首先的知道被保护区的概念。这是一段可能会产生异常信息的代码区,紧接着下面的代码就是处理这个异常信息的。
如果你在一个方法内部抛出了一个异常信息(或者你调用的一个方法抛出一个异常),这个方法抛出异常的时候就会退出执行。如果你不希望不抛出一个退出的方法,你可以建立一个特殊的块来捕获这个异常信息。这被叫做“Try”区,因为你在这里Try各种方法的调用。这个区域只是紧紧跟随关键词Try的一段普通代码区。
Ifyouwerecheckingforerrorscarefullyinaprogramminglanguagethatdidn’tsupportexceptionhandling,you’dhavetosurroundeverymethodcallwithsetupanderrortestingcode,evenifyoucallthesamemethodseveraltimes.Withexceptionhandling,youputeverythinginatryblockandcapturealltheexceptionsinoneplace.Thismeansyourcodeismucheasiertowriteandreadbecausethegoalofthecodeisnotconfusedwiththeerrorchecking.
你必须为了不支持异常处理程序的语言仔细的检查错误信息,你必须在每个方法周围写很多测试错误的代码,甚至你调用一个方法很多次。而有了异常处理程序之后,你把所有的程序放置到TryBlock,再把捕获异常信息的代码放到一个位置。这将使得你的代码书写更加简单,这样的话你的逻辑代码和错误检查代码就不会很复杂的交织在一起。
当然,抛出异常也必须在某些位置结束。而这个位置就是“异常处理程序”,这里你可以书写你希望捕获的所有类型的异常信息。异常处理程序紧紧跟随TryBlock区并且以关键词Catch来标识。
Eachcatchclause(exceptionhandler)islikealittlemethodthattakesoneandonlyoneargumentofaparticulartype.Theidentifier(id1,id2,andsoon)canbeusedinsidethehandler,justlikeamethodargument.Sometimesyouneverusetheidentifierbecausethetypeoftheexceptiongivesyouenoughinformationtodealwiththeexception,buttheidentifiermuststillbethere.
每个catch的句子都更像是一个小的方法,并且都拥有一个特定的类型参数。这些标识符可以在异常处理程序中使用,就像参数一样使用。有时候你可能根本用不到这些标识符因为他异常的类型已经提供了足够的信息来处理这个异常,但是你仍然要保留这个参数。
Thehandlersmustappeardirectlyafterthetryblock.Ifanexceptionisthrown,theexceptionhandlingmechanismgoeshuntingforthefirsthandlerwithanargumentthatmatchesthetypeoftheexception.Thenitentersthatcatchclause,andtheexceptionisconsideredhandled.Thesearchforhandlersstopsoncethecatchclauseisfinished.Onlythematchingcatchclauseexecutes;it’snotlikeaswitchstatementinwhichyouneedabreakaftereachcasetopreventtheremainingonesfromexecuting.
异常处理模块必须直接出现在TryBlock区之后。如果抛出了一个异常信息,那么异常处理机制会搜索第一个参数与异常类型匹配的处理程序。然后进入异常处理语句,这个异常就被认为得到了处理。当异常处理结束的时候搜索匹配的程序也就停止了。只有匹配的异常处理部分得到执行。它不像是switch语句还需要在每个case中使用break来阻止剩余部分继续执行。
Notethatwithinthetryblock,anumberofdifferentmethodcallsmightgeneratethesameexception,butyouneedonlyonehandler.
在异常处理理论中有两个基本的模型。在termination(java和C++支持),你认为这个异常信息太严重了没有办法回退到异常抛出的地方了。抛出这个异常则意味着没有办法可以解决,并且希望这个异常不要再回来了。
Thealternativeiscalledresumption.Itmeansthattheexceptionhandlerisexpectedtodosomethingtorectifythesituation,andthenthefaultingmethodisretried,presumingsuccessthesecondtime.Ifyouwantresumption,itmeansyoustillhopetocontinueexecutionaftertheexceptionishandled.Inthiscase,yourexceptionismorelikeamethodcall—whichishowyoushouldsetupsituationsinJavainwhichyouwantresumption-likebehavior.(Thatis,don’tthrowanexception;callamethodthatfixestheproblem.)Alternatively,placeyourtryblockinsideawhileloopthatkeepsreenteringthetryblockuntiltheresultissatisfactory.
另外一个被称为继续。它的意思是异常处理程序希望作某些工作来调整当前的情形,然后再执行出错的地方,希望第二次可以成功。如果你希望继续执行,那就是说你希望异常被处理之后还继续执行。在这种情况下,你的异常更像是方法调用-如果你想在Java中实现这种效果可以用这种办法来设置环境(也就是说不抛出吟唱而是调用一个方法来修补这个问题),
还有一种办法将你的tryblock区在得到满意的结果之前一直进入tryblock区。
Historically,programmersusingoperatingsystemsthatsupportedresumptiveexceptionhandlingeventuallyendedupusingtermination-likecodeandskippingresumption.Soalthoughresumptionsoundsattractiveatfirst,itisn’tquitesousefulinpractice.Thedominantreasonisprobablythecouplingthatresults;yourhandlermustoftenbeawareofwheretheexceptionisthrown,andcontainnongenericcodespecifictothethrowinglocation.Thismakesthecodedifficulttowriteandmaintain,especiallyforlargesystemswheretheexceptioncanbegeneratedfrommanypoints.
长久以来,程序员们都是一直使用支持继续模式的异常处理的操作系统,但是自己的代码确实一直使用终止模式来跳过继续执行。所以虽然继续模式听起来很吸引人,但是在实际当中还是很少使用的。最主要的原因还是担心它所导致的耦合。你的异常处理程序意识到哪里会抛出异常信息,并且需要泛型的代码来标识出异常抛出的地点。这使得代码非常难写和维护,尤其是对于那些很大的系统会有很多的地方抛出异常。
你应该不介意使用Java已经存在的异常信息。JDK的异常关系统并不能预知所有的错误你都想抛出,所以你可以建立异常类,来描述在自己的类库中可能遇到的问题。
Tocreateyourownexceptionclass,youmustinheritfromanexistingexceptionclass,preferablyonethatiscloseinmeaningtoyournewexception(althoughthisisoftennotpossible).Themosttrivialwaytocreateanewtypeofexceptionisjusttoletthecompilercreatethedefaultconstructorforyou,soitrequiresalmostnocodeatall:
创建一个自己的异常类,你必须继承一个已经存在的异常类,最好还在意思上和你要建立的异常类相近(虽然一般来讲是不太可能的)。创建新异常类的最简单的办法就是让编译器为你创建一个默认的构造方法,这样你几乎就不需要写任何代码了。
Thecompilercreatesadefaultconstructor,whichautomatically(andinvisibly)callsthebase-classdefaultconstructor.Ofcourse,inthiscaseyoudon’tgetaSimpleException(String)constructor,butinpracticethatisn’tusedmuch.Asyou’llsee,themostimportantthingaboutanexceptionistheclassname,somostofthetimeanexceptionliketheoneshownhereissatisfactory.
编译器自动的创建了一个构造方法,这个方法是不可见的,它会调用基类的构造方法。当然,这样你就没有SimpleException(String)的构造方法,但是在实际中这并不常见。就像你看到的最重要的是异常的类名,所以大部分时间这个异常也就足够了。
Here,theresultisprintedtotheconsolestandarderrorstreambywritingtoSystem.err.ThisisusuallyabetterplacetosenderrorinformationthanSystem.out,whichmayberedirected.IfyousendoutputtoSystem.err,itwillnotberedirectedalongwithSystem.outsotheuserismorelikelytonoticeit.
在这里System.err会把错误信息打印到控制台的standarderrorstream。这比将错误信息发送到System.out要好的多,因为后者可能会重新定向。如果你发送错误消息到System.err,它就不会被重新定向,这样就能更好的引起用户的注意。
YoucanalsocreateanexceptionclassthathasaconstructorwithaStringargument:
你也可以创建一个带有String参数的异常类
Theaddedcodeissmall:twoconstructorsthatdefinethewayMyExceptioniscreated.Inthesecondconstructor,thebase-classconstructorwithaStringargumentisexplicitlyinvokedbyusingthesuperkeyword.
添加了很少的代码,MyException类里面拥有了两个构造方法。在第二个构造方法中,基类中有String参数的方法也使用关键词super得到了调用。
Inthehandlers,oneoftheThrowable(fromwhichExceptionisinherited)methodsiscalled:printStackTrace().Thisproducesinformationaboutthesequenceofmethodsthatwerecalledtogettothepointwheretheexceptionhappened.Bydefault,theinformationgoestothestandarderrorstream,butoverloadedversionsallowyoutosendtheresultstoanyotherstreamaswell.
在异常处理程序中,调用了Throwable(Exception是从那里继承而来的)类中的printStackTrace()方法。它会产生一个关于“被调用的方法经历了一个怎样的顺序到达了错误的发生地点”,默认的这个错误信息会被送到标准的错误信息流,但是重载厚的版本可能允许你把它送到其它的信息流。
Theprocessofcreatingyourownexceptionscanbetakenfurther.Youcanaddextraconstructorsandmembers:
创建自己的异常类的过程可以走的更远,你可以增加一些额外的构造方法和成员。
Afieldihasbeenadded,alongwithamethodthatreadsthatvalueandanadditionalconstructorthatsetsit.Inaddition,Throwable.getMessage()hasbeenoverriddentoproduceamoreinterestingdetailmessage.getMessage()issomethingliketoString()forexceptionclasses.
增加了i数据成员,并且有一个方法读取这个值,另外的一个构造方法还有一个方法在构造方法中对其进行初始化。还有,Throwable.getMessage()被覆写为产生很有意思的详细信息了。这个方法在exception类中更像是toString()方法。
Sinceanexceptionisjustanotherkindofobject,youcancontinuethisprocessofembellishingthepowerofyourexceptionclasses.Keepinmind,however,thatallthisdressing-upmightbelostontheclientprogrammersusingyourpackages,sincetheymightsimplylookfortheexceptiontobethrownandnothingmore.(That’sthewaymostoftheJavalibraryexceptionsareused.)
因为异常和其它的对象没有什么区别,所以你可以继续修饰这个Exception类。但是记住,你修饰的这些在客户端程序员面前很可能丢失掉,因为他们仅仅是用它来抛出一个异常信息而已其它的什么都不需要。Java的Exception类库大部分都是这么使用的。
在Java中,鼓励你告诉那些调用你的方法的客户端程序员们,在你的方法中可能会抛出异常。这是很明智的做法,因为只有这样才知道如何书写代码来捕获那些潜在的异常信息。当然如果有源代码的话,客户端程序员可以查找源代码来寻找throw抛出异常的语句,但是一个类库通常是不提供源码的。要防止这个称为一个问题,Java提供了一种语法并且强制要求你使用的语法,可以让你很客气的告诉程序员这个方法会抛出什么类型的异常信息,这样客户端程序员就可以捕获它们。这就是异常的语法,它经常出现在方法的声明部分并且紧跟在参数的后面。
Theexceptionspecificationusesanadditionalkeyword,throws,followedbyalistofallthepotentialexceptiontypes.Soyourmethoddefinitionmightlooklikethis:
异常语法使用了一个其它的关键词throws,后面紧接着是一些可能会抛出的异常的类型,所以你的方法定义可能像下面这样了:
Ifyousay
itmeansthatnoexceptionsarethrownfromthemethod(exceptfortheexceptionsinheritedfromRuntimeException,whichcanbethrownanywherewithoutexceptionspecifications—thiswillbedescribedlater).
这的意思是这个方法不会抛出任何异常(除了从RuntimeException继承下来的异常信息,因为这个异常不需要声明就可以抛出来,这个我们稍后会讲到)。
Youcan’tlieaboutanexceptionspecification.Ifthecodewithinyourmethodcausesexceptions,butyourmethoddoesn’thandlethem,thecompilerwilldetectthisandtellyouthatyoumusteitherhandletheexceptionorindicatewithanexceptionspecificationthatitmaybethrownfromyourmethod.Byenforcingexceptionspecificationsfromtoptobottom,Javaguaranteesthatacertainlevelofexceptioncorrectnesscanbeensuredatcompiletime.
不要对异常说明说谎。如果你的代码发生了一个异常,而你没有去捕获它们,编译器会发现它并且告诉你要么捕获这个异常要么在异常说明部分提示这个方法会抛出异常。通过强化这种从顶层到底层的异常说明,Java在一定程序上在编译期就可以纠正你的异常。
Thereisoneplaceyoucanlie:Youcanclaimtothrowanexceptionthatyoureallydon’t.Thecompilertakesyourwordforit,andforcestheusersofyourmethodtotreatitasifitreallydoesthrowthatexception.Thishasthebeneficialeffectofbeingaplaceholderforthatexception,soyoucanactuallystartthrowingtheexceptionlaterwithoutrequiringchangestoexistingcode.It’salsoimportantforcreatingabstractbaseclassesandinterfaceswhosederivedclassesorimplementationsmayneedtothrowexceptions.
那是不是任何地方你都不能说谎了呢?你可以在根本不会抛出异常的地方要求抛出异常,编译器会当真的,它会强制要求使用这个方法的用户像处理这个真的会抛出异常一样处理。这么作的好处就是它先为异常占据了一个位置,当你真的想要抛出异常的时候不需要修改任何代码。这对于创建抽象基类和接口也很重要,因为它们的派生类和实现很可能需要抛出异常信息。
Exceptionsthatarecheckedandenforcedatcompiletimearecalledcheckedexceptions.
会在编译期间检查并且得到处理的异常叫做checkedexceptions;
创建一个能够处理任何类型的异常的异常处理程序是完全可行的。你可以通过捕获Exception的基类来完成,还有其它关于Exception的基类,但是这个和编程关系最相关。
Thiswillcatchanyexception,soifyouuseityou’llwanttoputitattheendofyourlistofhandlerstoavoidpreemptinganyexceptionhandlersthatmightotherwisefollowit.
这个能够捕获任何异常信息,所以如果你使用的话你需要把它放到所有的异常类型的最后,避免它在其它更详尽的异常前面把异常抛出。
SincetheExceptionclassisthebaseofalltheexceptionclassesthatareimportanttotheprogrammer,youdon’tgetmuchspecificinformationabouttheexception,butyoucancallthemethodsthatcomefromitsbasetypeThrowable:
因为Exception是所有异常信息的基类,所以这个对于程序员来讲是很重要的,你没能得到更多的关于exception的信息,但是你可以调用它的基类Throwable的方法。
StringgetMessage()
StringgetLocalizedMessage()
Getsthedetailmessage,oramessageadjustedforthisparticularlocale.
StringtoString()
ReturnsashortdescriptionoftheThrowable,includingthedetailmessageifthereisone.
voidprintStackTrace()
voidprintStackTrace(PrintStream)
voidprintStackTrace(java.io.PrintWriter)
PrintstheThrowableandtheThrowable’scallstacktrace.Thecallstackshowsthesequenceofmethodcallsthatbroughtyoutothepointatwhichtheexceptionwasthrown.Thefirstversionprintstostandarderror,thesecondandthirdprintstoastreamofyourchoice(inChapter12,you’llunderstandwhytherearetwotypesofstreams).
ThrowablefillInStackTrace()
RecordsinformationwithinthisThrowableobjectaboutthecurrentstateofthestackframes.Usefulwhenanapplicationisrethrowinganerrororexception(moreaboutthisshortly).
Inaddition,yougetsomeothermethodsfromThrowable’sbasetypeObject(everybody’sbasetype).TheonethatmightcomeinhandyforexceptionsisgetClass(),whichreturnsanobjectrepresentingtheclassofthisobject.YoucaninturnquerythisClassobjectforitsnamewithgetName().YoucanalsodomoresophisticatedthingswithClassobjectsthataren’tnecessaryinexceptionhandling.
还有,你可以调用Throwable的基类Object的一些方法。有一个方法是很有用的,getClass(),它返回了这个对象所属的类的描述,你可以使用getName()查询这个类型的对象的名字,当然你还可以在Object类得到更多有用的东西,但是对异常处理就不是很关键了。
Here’sanexamplethatshowstheuseofthebasicExceptionmethods:
下面给出一个例子来展示基本的Exception的方法:
Youcanseethatthemethodsprovidesuccessivelymoreinformation—eachiseffectivelyasupersetofthepreviousone.
你能看到这些方法一个比一个提供了更多的信息,其实它们每一个都是前面一个的超集。
典型来说,你使用Exception捕获了所有的异常信息,有时候你可能会想把刚才捕获的异常信息再抛出去,因为你已经获得了这个异常的对象,所以可以很简单的把这个对象再抛出去。
Rethrowinganexceptioncausesittogototheexceptionhandlersinthenext-highercontext.Anyfurthercatchclausesforthesametryblockarestillignored.Inaddition,everythingabouttheexceptionobjectispreserved,sothehandleratthehighercontextthatcatchesthespecificexceptiontypecanextractalltheinformationfromthatobject.
重抛异常信息将这个异常跑出到一个更外层的环境中,所有针对于try语句中的catch语句将都被忽略。当然异常对象中的所有信息都得到了报出,所以当外层捕获这个异常信息的时候就能分解出这个对象的所有信息了。
Ifyousimplyrethrowthecurrentexception,theinformationthatyouprintaboutthatexceptioninprintStackTrace()willpertaintotheexception’sorigin,nottheplacewhereyourethrowit.Ifyouwanttoinstallnewstacktraceinformation,youcandosobycallingfillInStackTrace(),whichreturnsaThrowableobjectthatitcreatesbystuffingthecurrentstackinformationintotheoldexceptionobject.Here’swhatitlookslike:
如果你只是简单的抛出异常信息,关于异常对象的信息你可以使用printStackTrace()打印出来,它指向的是抛出异常的原始位置而不是再次抛出异常的位置。如果你还希望转载一些栈轨迹信息,你可以使用fillInStackTrace()方法,这个方法会将当前的栈信息写进就的异常对象中并返回一个Throwable的对象,下面就是它的工作方式:
Theimportantlinenumbersaremarkedascomments.Withline17uncommented(asshown),theoutputisasshown,sotheexceptionstacktracealwaysremembersitstruepointoforiginnomatterhowmanytimesitgetsrethrown.
最重要的被标识为注释了,而第17行代码我们看到了输出结果,所以这个异常信息始终记得它被抛出的原始位置,不管被抛出了多少次
Withline17commentedandline18uncommented,fillInStackTrace()isusedinstead,andtheresultis:
如果将第17行注释掉而把18行执行,那么fillInStackTrace()会被执行,结果就是:
(PlusadditionalcomplaintsfromtheTest.expect()method.)BecauseoffillInStackTrace(),line18becomesthenewpointoforiginoftheexception.
加上一些Test.expect()方法的错误信息加上finllInStackTrace(),第18行就称为了新的异常的抛出地了。
TheclassThrowablemustappearintheexceptionspecificationforg()andmain()becausefillInStackTrace()producesareferencetoaThrowableobject.SinceThrowableisabaseclassofException,it’spossibletogetanobjectthat’saThrowablebutnotanException,sothehandlerforExceptioninmain()mightmissit.Tomakesureeverythingisinorder,thecompilerforcesanexceptionspecificationforThrowable.Forexample,theexceptioninthefollowingprogramisnotcaughtinmain():
Throwable类必须要出现在g()和main()方法中,因为fillInStackTrace()方法返回了一个Throwable的对象。因为Throwable是Exception的基类,所以在main方法中捕获Exception异常则可能会捕获不到,为了确保程序的有序执行,编译器强制要求在异常说明中使用Throwable。举例来讲,下面的程序中main方法就没有捕获到exception信息。
It’salsopossibletorethrowadifferentexceptionfromtheoneyoucaught.Ifyoudothis,yougetasimilareffectaswhenyouusefillInStackTrace()—theinformationabouttheoriginalsiteoftheexceptionislost,andwhatyou’releftwithistheinformationpertainingtothenewthrow:
在你捕获异常的地方你可以抛出一个新的异常信息也是可以的。如果你这么做的话,你得到的结果和刚才调用fillInStackTrace()的效果是一样的,原始位置发生异常信息都丢失了,所留下的只是异常再次被抛出时异常的信息了。
Thefinalexceptionknowsonlythatitcamefrommain()andnotfromf().
最后的异常对象只是知道这个异常来自main(),而不是f();
Youneverhavetoworryaboutcleaningupthepreviousexception,oranyexceptionsforthatmatter.They’reallheap-basedobjectscreatedwithnew,sothegarbagecollectorautomaticallycleansthemallup.
你不必担心清理这些建立的异常对象,或者其它的异常信息。它们都是通过new方法在堆中创建的,所以垃圾回收器会自动的将它们清理掉。
往往我们希望捕获了一个异常但是抛出了另外一个异常信息,而且希望记录原始的异常的信息,这叫做“异常链”。早在JDK1.4的时候程序员们就不得不去写代码来保存原始的异常信息,但是现在所有的Throwable的子类的构造方法都能接受一个cause对象,这个cause就是来保存上一个异常的,这样通过你维护的栈轨迹就可用回退到异常原始发生的,即便是你在这个位置创建并且抛出了新的异常也可以。
It’sinterestingtonotethattheonlyThrowablesubclassesthatprovidethecauseargumentintheconstructorarethethreefundamentalexceptionclassesError(usedbytheJVMtoreportsystemerrors),Exception,andRuntimeException.Ifyouwanttochainanyotherexceptiontypes,youdoitthroughtheinitCause()methodratherthantheconstructor.
比较有意思的是Throwable子类中能够支持cause参数构造方法的一共有三个基础异常类,分别是:Error(供JVM报告系统错误使用),Exception和RuntimeException;如果你要链接其它的异常类,那么你只能使用initCause()方法而不能使用构造方法了。
Here’sanexamplethatallowsyoutodynamicallyaddfieldstoaDynamicFieldsobjectatruntime:
下面给出一个例子,允许你运行时动态的在DynamicFields对象中增加数据项;
Throw关键词引发了一系列相关的事情,典型的,你首先会用new方法创建一个对象来描述异常的条件,将这个对象给了throw方法。这个方法会返回这个对象,尽管它的类型不是这个方法设计所返回的类型。一种简单的方式来看“异常处理程序”就是一种不同返回类型的机制,但是这种机制不要走的太远否则就麻烦了,你也可以在一段顺序执行的区域采用throw一个异常来退出程序。但是返回结果,退出了方法或者作用域。
Anysimilaritytoanordinaryreturnfromamethodendshere,becausewhereyoureturnissomeplacecompletelydifferentfromwhereyoureturnforanormalmethodcall.(Youendupinanappropriateexceptionhandlerthatmightbefar—manylevelsawayonthecallstack—fromwheretheexceptionwasthrown.)
异常处理和正常程序的返回相同点就是这些了。因为你抛出异常的地方和你返回正常发发调用的地方有着本质的区别。你解决这个异常信息的地方可能距离异常的抛出地方很远。
Inaddition,youcanthrowanytypeofThrowable(theexceptionrootclass)objectthatyouwant.Typically,you’llthrowadifferentclassofexceptionforeachdifferenttypeoferror.Theinformationabouttheerrorisrepresentedbothinsidetheexceptionobjectandimplicitlyinthenameoftheexceptionclass,sosomeoneinthebiggercontextcanfigureoutwhattodowithyourexception.(Often,theonlyinformationisthetypeofexception,andnothingmeaningfulisstoredwithintheexceptionobject.)
另外,你可以抛出任何类型的以Throwable为基类的对象。典型的,你会根据不同的错误抛出不同类型的异常信息。关于错误的信息的描述在对象里并且暗含在类的名字当中,所以在更高的环境中可以识别出根据异常信息如何处理。(通常,有用的信息就是这个对象的类型,而在对象中存储的信息没有什么意义)
Catchinganexception
Ifamethodthrowsanexception,itmustassumethatexceptionwillbe“caught”anddealtwith.Oneoftheadvantagesofexceptionhandlingisthatitallowsyoutoconcentrateontheproblemyou’retryingtosolveinoneplace,andthendealwiththeerrorsfromthatcodeinanotherplace.如果一个方法抛出了一个异常,它必须确保这个异常信息可以被捕获和处理掉。“异常处理程序”的优势之一就是它允许你在一个地方集中精力解决这个问题,而到另外一个地方来解决这个异常。
Toseehowanexceptioniscaught,youmustfirstunderstandtheconceptofaguardedregion.Thisisasectionofcodethatmightproduceexceptionsandisfollowedbythecodetohandlethoseexceptions.
要想知道如何捕获异常,你必须首先的知道被保护区的概念。这是一段可能会产生异常信息的代码区,紧接着下面的代码就是处理这个异常信息的。
Thetryblock
Ifyou’reinsideamethodandyouthrowanexception(oranothermethodyoucallwithinthismethodthrowsanexception),thatmethodwillexitintheprocessofthrowing.Ifyoudon’twantathrowtoexitthemethod,youcansetupaspecialblockwithinthatmethodtocapturetheexception.Thisiscalledthetryblockbecauseyou“try”yourvariousmethodcallsthere.Thetryblockisanordinaryscopeprecededbythekeywordtry:如果你在一个方法内部抛出了一个异常信息(或者你调用的一个方法抛出一个异常),这个方法抛出异常的时候就会退出执行。如果你不希望不抛出一个退出的方法,你可以建立一个特殊的块来捕获这个异常信息。这被叫做“Try”区,因为你在这里Try各种方法的调用。这个区域只是紧紧跟随关键词Try的一段普通代码区。
try{
//Codethatmightgenerateexceptions
}
Ifyouwerecheckingforerrorscarefullyinaprogramminglanguagethatdidn’tsupportexceptionhandling,you’dhavetosurroundeverymethodcallwithsetupanderrortestingcode,evenifyoucallthesamemethodseveraltimes.Withexceptionhandling,youputeverythinginatryblockandcapturealltheexceptionsinoneplace.Thismeansyourcodeismucheasiertowriteandreadbecausethegoalofthecodeisnotconfusedwiththeerrorchecking.
你必须为了不支持异常处理程序的语言仔细的检查错误信息,你必须在每个方法周围写很多测试错误的代码,甚至你调用一个方法很多次。而有了异常处理程序之后,你把所有的程序放置到TryBlock,再把捕获异常信息的代码放到一个位置。这将使得你的代码书写更加简单,这样的话你的逻辑代码和错误检查代码就不会很复杂的交织在一起。
Exceptionhandlers
Ofcourse,thethrownexceptionmustendupsomeplace.This“place”istheexceptionhandler,andthere’soneforeveryexceptiontypeyouwanttocatch.Exceptionhandlersimmediatelyfollowthetryblockandaredenotedbythekeywordcatch:当然,抛出异常也必须在某些位置结束。而这个位置就是“异常处理程序”,这里你可以书写你希望捕获的所有类型的异常信息。异常处理程序紧紧跟随TryBlock区并且以关键词Catch来标识。
try{
//Codethatmightgenerateexceptions
}catch(Type1id1){
//HandleexceptionsofType1
}catch(Type2id2){
//HandleexceptionsofType2
}catch(Type3id3){
//HandleexceptionsofType3
}
//etc...
Eachcatchclause(exceptionhandler)islikealittlemethodthattakesoneandonlyoneargumentofaparticulartype.Theidentifier(id1,id2,andsoon)canbeusedinsidethehandler,justlikeamethodargument.Sometimesyouneverusetheidentifierbecausethetypeoftheexceptiongivesyouenoughinformationtodealwiththeexception,buttheidentifiermuststillbethere.
每个catch的句子都更像是一个小的方法,并且都拥有一个特定的类型参数。这些标识符可以在异常处理程序中使用,就像参数一样使用。有时候你可能根本用不到这些标识符因为他异常的类型已经提供了足够的信息来处理这个异常,但是你仍然要保留这个参数。
Thehandlersmustappeardirectlyafterthetryblock.Ifanexceptionisthrown,theexceptionhandlingmechanismgoeshuntingforthefirsthandlerwithanargumentthatmatchesthetypeoftheexception.Thenitentersthatcatchclause,andtheexceptionisconsideredhandled.Thesearchforhandlersstopsoncethecatchclauseisfinished.Onlythematchingcatchclauseexecutes;it’snotlikeaswitchstatementinwhichyouneedabreakaftereachcasetopreventtheremainingonesfromexecuting.
异常处理模块必须直接出现在TryBlock区之后。如果抛出了一个异常信息,那么异常处理机制会搜索第一个参数与异常类型匹配的处理程序。然后进入异常处理语句,这个异常就被认为得到了处理。当异常处理结束的时候搜索匹配的程序也就停止了。只有匹配的异常处理部分得到执行。它不像是switch语句还需要在每个case中使用break来阻止剩余部分继续执行。
Notethatwithinthetryblock,anumberofdifferentmethodcallsmightgeneratethesameexception,butyouneedonlyonehandler.
需要说明的是在Tryblock,不同的方法可能会产生一个同样的异常信息,但是我们只需要处理一个就足够了。
Terminationvs.resumption
Therearetwobasicmodelsinexceptionhandlingtheory.Intermination(whichiswhatJavaandC++support),youassumethattheerrorissocriticalthatthere’snowaytogetbacktowheretheexceptionoccurred.Whoeverthrewtheexceptiondecidedthattherewasnowaytosalvagethesituation,andtheydon’twanttocomeback.在异常处理理论中有两个基本的模型。在termination(java和C++支持),你认为这个异常信息太严重了没有办法回退到异常抛出的地方了。抛出这个异常则意味着没有办法可以解决,并且希望这个异常不要再回来了。
Thealternativeiscalledresumption.Itmeansthattheexceptionhandlerisexpectedtodosomethingtorectifythesituation,andthenthefaultingmethodisretried,presumingsuccessthesecondtime.Ifyouwantresumption,itmeansyoustillhopetocontinueexecutionaftertheexceptionishandled.Inthiscase,yourexceptionismorelikeamethodcall—whichishowyoushouldsetupsituationsinJavainwhichyouwantresumption-likebehavior.(Thatis,don’tthrowanexception;callamethodthatfixestheproblem.)Alternatively,placeyourtryblockinsideawhileloopthatkeepsreenteringthetryblockuntiltheresultissatisfactory.
另外一个被称为继续。它的意思是异常处理程序希望作某些工作来调整当前的情形,然后再执行出错的地方,希望第二次可以成功。如果你希望继续执行,那就是说你希望异常被处理之后还继续执行。在这种情况下,你的异常更像是方法调用-如果你想在Java中实现这种效果可以用这种办法来设置环境(也就是说不抛出吟唱而是调用一个方法来修补这个问题),
还有一种办法将你的tryblock区在得到满意的结果之前一直进入tryblock区。
Historically,programmersusingoperatingsystemsthatsupportedresumptiveexceptionhandlingeventuallyendedupusingtermination-likecodeandskippingresumption.Soalthoughresumptionsoundsattractiveatfirst,itisn’tquitesousefulinpractice.Thedominantreasonisprobablythecouplingthatresults;yourhandlermustoftenbeawareofwheretheexceptionisthrown,andcontainnongenericcodespecifictothethrowinglocation.Thismakesthecodedifficulttowriteandmaintain,especiallyforlargesystemswheretheexceptioncanbegeneratedfrommanypoints.
长久以来,程序员们都是一直使用支持继续模式的异常处理的操作系统,但是自己的代码确实一直使用终止模式来跳过继续执行。所以虽然继续模式听起来很吸引人,但是在实际当中还是很少使用的。最主要的原因还是担心它所导致的耦合。你的异常处理程序意识到哪里会抛出异常信息,并且需要泛型的代码来标识出异常抛出的地点。这使得代码非常难写和维护,尤其是对于那些很大的系统会有很多的地方抛出异常。
Creatingyourownexceptions
You’renotstuckusingtheexistingJavaexceptions.TheJDKexceptionhierarchycan’tforeseealltheerrorsyoumightwanttoreport,soyoucancreateyourowntodenoteaspecialproblemthatyourlibrarymightencounter.你应该不介意使用Java已经存在的异常信息。JDK的异常关系统并不能预知所有的错误你都想抛出,所以你可以建立异常类,来描述在自己的类库中可能遇到的问题。
Tocreateyourownexceptionclass,youmustinheritfromanexistingexceptionclass,preferablyonethatiscloseinmeaningtoyournewexception(althoughthisisoftennotpossible).Themosttrivialwaytocreateanewtypeofexceptionisjusttoletthecompilercreatethedefaultconstructorforyou,soitrequiresalmostnocodeatall:
创建一个自己的异常类,你必须继承一个已经存在的异常类,最好还在意思上和你要建立的异常类相近(虽然一般来讲是不太可能的)。创建新异常类的最简单的办法就是让编译器为你创建一个默认的构造方法,这样你几乎就不需要写任何代码了。
importcom.bruceeckel.simpletest.*;
classSimpleExceptionextendsException{}
publicclassSimpleExceptionDemo{
privatestaticTestmonitor=newTest();
publicvoidf()throwsSimpleException{
System.out.println("ThrowSimpleExceptionfromf()");
thrownewSimpleException();
}
publicstaticvoidmain(String[]args){
SimpleExceptionDemosed=newSimpleExceptionDemo();
try{
sed.f();
}catch(SimpleExceptione){
System.err.println("Caughtit!");
}
monitor.expect(newString[]{
"ThrowSimpleExceptionfromf()",
"Caughtit!"
});
}
}
Thecompilercreatesadefaultconstructor,whichautomatically(andinvisibly)callsthebase-classdefaultconstructor.Ofcourse,inthiscaseyoudon’tgetaSimpleException(String)constructor,butinpracticethatisn’tusedmuch.Asyou’llsee,themostimportantthingaboutanexceptionistheclassname,somostofthetimeanexceptionliketheoneshownhereissatisfactory.
编译器自动的创建了一个构造方法,这个方法是不可见的,它会调用基类的构造方法。当然,这样你就没有SimpleException(String)的构造方法,但是在实际中这并不常见。就像你看到的最重要的是异常的类名,所以大部分时间这个异常也就足够了。
Here,theresultisprintedtotheconsolestandarderrorstreambywritingtoSystem.err.ThisisusuallyabetterplacetosenderrorinformationthanSystem.out,whichmayberedirected.IfyousendoutputtoSystem.err,itwillnotberedirectedalongwithSystem.outsotheuserismorelikelytonoticeit.
在这里System.err会把错误信息打印到控制台的standarderrorstream。这比将错误信息发送到System.out要好的多,因为后者可能会重新定向。如果你发送错误消息到System.err,它就不会被重新定向,这样就能更好的引起用户的注意。
YoucanalsocreateanexceptionclassthathasaconstructorwithaStringargument:
你也可以创建一个带有String参数的异常类
importcom.bruceeckel.simpletest.*;
classMyExceptionextendsException{
publicMyException(){}
publicMyException(Stringmsg){super(msg);}
}
publicclassFullConstructors{
privatestaticTestmonitor=newTest();
publicstaticvoidf()throwsMyException{
System.out.println("ThrowingMyExceptionfromf()");
thrownewMyException();
}
publicstaticvoidg()throwsMyException{
System.out.println("ThrowingMyExceptionfromg()");
thrownewMyException("Originateding()");
}
publicstaticvoidmain(String[]args){
try{
f();
}catch(MyExceptione){
e.printStackTrace();
}
try{
g();
}catch(MyExceptione){
e.printStackTrace();
}
monitor.expect(newString[]{
"ThrowingMyExceptionfromf()",
"MyException",
"%%/tatFullConstructors.f//(.*//)",
"%%/tatFullConstructors.main//(.*//)",
"ThrowingMyExceptionfromg()",
"MyException:Originateding()",
"%%/tatFullConstructors.g//(.*//)",
"%%/tatFullConstructors.main//(.*//)"
});
}
}
Theaddedcodeissmall:twoconstructorsthatdefinethewayMyExceptioniscreated.Inthesecondconstructor,thebase-classconstructorwithaStringargumentisexplicitlyinvokedbyusingthesuperkeyword.
添加了很少的代码,MyException类里面拥有了两个构造方法。在第二个构造方法中,基类中有String参数的方法也使用关键词super得到了调用。
Inthehandlers,oneoftheThrowable(fromwhichExceptionisinherited)methodsiscalled:printStackTrace().Thisproducesinformationaboutthesequenceofmethodsthatwerecalledtogettothepointwheretheexceptionhappened.Bydefault,theinformationgoestothestandarderrorstream,butoverloadedversionsallowyoutosendtheresultstoanyotherstreamaswell.
在异常处理程序中,调用了Throwable(Exception是从那里继承而来的)类中的printStackTrace()方法。它会产生一个关于“被调用的方法经历了一个怎样的顺序到达了错误的发生地点”,默认的这个错误信息会被送到标准的错误信息流,但是重载厚的版本可能允许你把它送到其它的信息流。
Theprocessofcreatingyourownexceptionscanbetakenfurther.Youcanaddextraconstructorsandmembers:
创建自己的异常类的过程可以走的更远,你可以增加一些额外的构造方法和成员。
importcom.bruceeckel.simpletest.*;
classMyException2extendsException{
privateintx;
publicMyException2(){}
publicMyException2(Stringmsg){super(msg);}
publicMyException2(Stringmsg,intx){
super(msg);
this.x=x;
}
publicintval(){returnx;}
publicStringgetMessage(){
return"DetailMessage:"+x+""+super.getMessage();
}
}
publicclassExtraFeatures{
privatestaticTestmonitor=newTest();
publicstaticvoidf()throwsMyException2{
System.out.println("ThrowingMyException2fromf()");
thrownewMyException2();
}
publicstaticvoidg()throwsMyException2{
System.out.println("ThrowingMyException2fromg()");
thrownewMyException2("Originateding()");
}
publicstaticvoidh()throwsMyException2{
System.out.println("ThrowingMyException2fromh()");
thrownewMyException2("Originatedinh()",47);
}
publicstaticvoidmain(String[]args){
try{
f();
}catch(MyException2e){
e.printStackTrace();
}
try{
g();
}catch(MyException2e){
e.printStackTrace();
}
try{
h();
}catch(MyException2e){
e.printStackTrace();
System.err.println("e.val()="+e.val());
}
monitor.expect(newString[]{
"ThrowingMyException2fromf()",
"MyException2:DetailMessage:0null",
"%%/tatExtraFeatures.f//(.*//)",
"%%/tatExtraFeatures.main//(.*//)",
"ThrowingMyException2fromg()",
"MyException2:DetailMessage:0Originateding()",
"%%/tatExtraFeatures.g//(.*//)",
"%%/tatExtraFeatures.main//(.*//)",
"ThrowingMyException2fromh()",
"MyException2:DetailMessage:47Originatedinh()",
"%%/tatExtraFeatures.h//(.*//)",
"%%/tatExtraFeatures.main//(.*//)",
"e.val()=47"
});
}
}
Afieldihasbeenadded,alongwithamethodthatreadsthatvalueandanadditionalconstructorthatsetsit.Inaddition,Throwable.getMessage()hasbeenoverriddentoproduceamoreinterestingdetailmessage.getMessage()issomethingliketoString()forexceptionclasses.
增加了i数据成员,并且有一个方法读取这个值,另外的一个构造方法还有一个方法在构造方法中对其进行初始化。还有,Throwable.getMessage()被覆写为产生很有意思的详细信息了。这个方法在exception类中更像是toString()方法。
Sinceanexceptionisjustanotherkindofobject,youcancontinuethisprocessofembellishingthepowerofyourexceptionclasses.Keepinmind,however,thatallthisdressing-upmightbelostontheclientprogrammersusingyourpackages,sincetheymightsimplylookfortheexceptiontobethrownandnothingmore.(That’sthewaymostoftheJavalibraryexceptionsareused.)
因为异常和其它的对象没有什么区别,所以你可以继续修饰这个Exception类。但是记住,你修饰的这些在客户端程序员面前很可能丢失掉,因为他们仅仅是用它来抛出一个异常信息而已其它的什么都不需要。Java的Exception类库大部分都是这么使用的。
Theexceptionspecification
InJava,you’reencouragedtoinformtheclientprogrammer,whocallsyourmethod,oftheexceptionsthatmightbethrownfromyourmethod.Thisiscivilized,becausethecallercanknowexactlywhatcodetowritetocatchallpotentialexceptions.Ofcourse,ifsourcecodeisavailable,theclientprogrammercouldhuntthroughandlookforthrowstatements,butoftenalibrarydoesn’tcomewithsources.Topreventthisfrombeingaproblem,Javaprovidessyntax(andforcesyoutousethatsyntax)toallowyoutopolitelytelltheclientprogrammerwhatexceptionsthismethodthrows,sotheclientprogrammercanhandlethem.Thisistheexceptionspecificationandit’spartofthemethoddeclaration,appearingaftertheargumentlist.在Java中,鼓励你告诉那些调用你的方法的客户端程序员们,在你的方法中可能会抛出异常。这是很明智的做法,因为只有这样才知道如何书写代码来捕获那些潜在的异常信息。当然如果有源代码的话,客户端程序员可以查找源代码来寻找throw抛出异常的语句,但是一个类库通常是不提供源码的。要防止这个称为一个问题,Java提供了一种语法并且强制要求你使用的语法,可以让你很客气的告诉程序员这个方法会抛出什么类型的异常信息,这样客户端程序员就可以捕获它们。这就是异常的语法,它经常出现在方法的声明部分并且紧跟在参数的后面。
Theexceptionspecificationusesanadditionalkeyword,throws,followedbyalistofallthepotentialexceptiontypes.Soyourmethoddefinitionmightlooklikethis:
异常语法使用了一个其它的关键词throws,后面紧接着是一些可能会抛出的异常的类型,所以你的方法定义可能像下面这样了:
voidf()throwsTooBig,TooSmall,DivZero{//...
Ifyousay
voidf(){//...
itmeansthatnoexceptionsarethrownfromthemethod(exceptfortheexceptionsinheritedfromRuntimeException,whichcanbethrownanywherewithoutexceptionspecifications—thiswillbedescribedlater).
这的意思是这个方法不会抛出任何异常(除了从RuntimeException继承下来的异常信息,因为这个异常不需要声明就可以抛出来,这个我们稍后会讲到)。
Youcan’tlieaboutanexceptionspecification.Ifthecodewithinyourmethodcausesexceptions,butyourmethoddoesn’thandlethem,thecompilerwilldetectthisandtellyouthatyoumusteitherhandletheexceptionorindicatewithanexceptionspecificationthatitmaybethrownfromyourmethod.Byenforcingexceptionspecificationsfromtoptobottom,Javaguaranteesthatacertainlevelofexceptioncorrectnesscanbeensuredatcompiletime.
不要对异常说明说谎。如果你的代码发生了一个异常,而你没有去捕获它们,编译器会发现它并且告诉你要么捕获这个异常要么在异常说明部分提示这个方法会抛出异常。通过强化这种从顶层到底层的异常说明,Java在一定程序上在编译期就可以纠正你的异常。
Thereisoneplaceyoucanlie:Youcanclaimtothrowanexceptionthatyoureallydon’t.Thecompilertakesyourwordforit,andforcestheusersofyourmethodtotreatitasifitreallydoesthrowthatexception.Thishasthebeneficialeffectofbeingaplaceholderforthatexception,soyoucanactuallystartthrowingtheexceptionlaterwithoutrequiringchangestoexistingcode.It’salsoimportantforcreatingabstractbaseclassesandinterfaceswhosederivedclassesorimplementationsmayneedtothrowexceptions.
那是不是任何地方你都不能说谎了呢?你可以在根本不会抛出异常的地方要求抛出异常,编译器会当真的,它会强制要求使用这个方法的用户像处理这个真的会抛出异常一样处理。这么作的好处就是它先为异常占据了一个位置,当你真的想要抛出异常的时候不需要修改任何代码。这对于创建抽象基类和接口也很重要,因为它们的派生类和实现很可能需要抛出异常信息。
Exceptionsthatarecheckedandenforcedatcompiletimearecalledcheckedexceptions.
会在编译期间检查并且得到处理的异常叫做checkedexceptions;
Catchinganyexception
Itispossibletocreateahandlerthatcatchesanytypeofexception.Youdothisbycatchingthebase-classexceptiontypeException(thereareothertypesofbaseexceptions,butExceptionisthebasethat’spertinenttovirtuallyallprogrammingactivities):catch(Exceptione){
System.err.println("Caughtanexception");
}
创建一个能够处理任何类型的异常的异常处理程序是完全可行的。你可以通过捕获Exception的基类来完成,还有其它关于Exception的基类,但是这个和编程关系最相关。
Thiswillcatchanyexception,soifyouuseityou’llwanttoputitattheendofyourlistofhandlerstoavoidpreemptinganyexceptionhandlersthatmightotherwisefollowit.
这个能够捕获任何异常信息,所以如果你使用的话你需要把它放到所有的异常类型的最后,避免它在其它更详尽的异常前面把异常抛出。
SincetheExceptionclassisthebaseofalltheexceptionclassesthatareimportanttotheprogrammer,youdon’tgetmuchspecificinformationabouttheexception,butyoucancallthemethodsthatcomefromitsbasetypeThrowable:
因为Exception是所有异常信息的基类,所以这个对于程序员来讲是很重要的,你没能得到更多的关于exception的信息,但是你可以调用它的基类Throwable的方法。
StringgetMessage()
StringgetLocalizedMessage()
Getsthedetailmessage,oramessageadjustedforthisparticularlocale.
StringtoString()
ReturnsashortdescriptionoftheThrowable,includingthedetailmessageifthereisone.
voidprintStackTrace()
voidprintStackTrace(PrintStream)
voidprintStackTrace(java.io.PrintWriter)
PrintstheThrowableandtheThrowable’scallstacktrace.Thecallstackshowsthesequenceofmethodcallsthatbroughtyoutothepointatwhichtheexceptionwasthrown.Thefirstversionprintstostandarderror,thesecondandthirdprintstoastreamofyourchoice(inChapter12,you’llunderstandwhytherearetwotypesofstreams).
ThrowablefillInStackTrace()
RecordsinformationwithinthisThrowableobjectaboutthecurrentstateofthestackframes.Usefulwhenanapplicationisrethrowinganerrororexception(moreaboutthisshortly).
Inaddition,yougetsomeothermethodsfromThrowable’sbasetypeObject(everybody’sbasetype).TheonethatmightcomeinhandyforexceptionsisgetClass(),whichreturnsanobjectrepresentingtheclassofthisobject.YoucaninturnquerythisClassobjectforitsnamewithgetName().YoucanalsodomoresophisticatedthingswithClassobjectsthataren’tnecessaryinexceptionhandling.
还有,你可以调用Throwable的基类Object的一些方法。有一个方法是很有用的,getClass(),它返回了这个对象所属的类的描述,你可以使用getName()查询这个类型的对象的名字,当然你还可以在Object类得到更多有用的东西,但是对异常处理就不是很关键了。
Here’sanexamplethatshowstheuseofthebasicExceptionmethods:
下面给出一个例子来展示基本的Exception的方法:
importcom.bruceeckel.simpletest.*;
publicclassExceptionMethods{
privatestaticTestmonitor=newTest();
publicstaticvoidmain(String[]args){
try{
thrownewException("MyException");
}catch(Exceptione){
System.err.println("CaughtException");
System.err.println("getMessage():"+e.getMessage());
System.err.println("getLocalizedMessage():"+
e.getLocalizedMessage());
System.err.println("toString():"+e);
System.err.println("printStackTrace():");
e.printStackTrace();
}
monitor.expect(newString[]{
"CaughtException",
"getMessage():MyException",
"getLocalizedMessage():MyException",
"toString():java.lang.Exception:MyException",
"printStackTrace():",
"java.lang.Exception:MyException",
"%%/tatExceptionMethods.main//(.*//)"
});
}
}
Youcanseethatthemethodsprovidesuccessivelymoreinformation—eachiseffectivelyasupersetofthepreviousone.
你能看到这些方法一个比一个提供了更多的信息,其实它们每一个都是前面一个的超集。
Rethrowinganexception
Sometimesyou’llwanttorethrowtheexceptionthatyoujustcaught,particularlywhenyouuseExceptiontocatchanyexception.Sinceyoualreadyhavethereferencetothecurrentexception,youcansimplyrethrowthatreference:典型来说,你使用Exception捕获了所有的异常信息,有时候你可能会想把刚才捕获的异常信息再抛出去,因为你已经获得了这个异常的对象,所以可以很简单的把这个对象再抛出去。
catch(Exceptione){
System.err.println("Anexceptionwasthrown");
throwe;
}
Rethrowinganexceptioncausesittogototheexceptionhandlersinthenext-highercontext.Anyfurthercatchclausesforthesametryblockarestillignored.Inaddition,everythingabouttheexceptionobjectispreserved,sothehandleratthehighercontextthatcatchesthespecificexceptiontypecanextractalltheinformationfromthatobject.
重抛异常信息将这个异常跑出到一个更外层的环境中,所有针对于try语句中的catch语句将都被忽略。当然异常对象中的所有信息都得到了报出,所以当外层捕获这个异常信息的时候就能分解出这个对象的所有信息了。
Ifyousimplyrethrowthecurrentexception,theinformationthatyouprintaboutthatexceptioninprintStackTrace()willpertaintotheexception’sorigin,nottheplacewhereyourethrowit.Ifyouwanttoinstallnewstacktraceinformation,youcandosobycallingfillInStackTrace(),whichreturnsaThrowableobjectthatitcreatesbystuffingthecurrentstackinformationintotheoldexceptionobject.Here’swhatitlookslike:
如果你只是简单的抛出异常信息,关于异常对象的信息你可以使用printStackTrace()打印出来,它指向的是抛出异常的原始位置而不是再次抛出异常的位置。如果你还希望转载一些栈轨迹信息,你可以使用fillInStackTrace()方法,这个方法会将当前的栈信息写进就的异常对象中并返回一个Throwable的对象,下面就是它的工作方式:
importcom.bruceeckel.simpletest.*;
publicclassRethrowing{
privatestaticTestmonitor=newTest();
publicstaticvoidf()throwsException{
System.out.println("originatingtheexceptioninf()");
thrownewException("thrownfromf()");
}
publicstaticvoidg()throwsThrowable{
try{
f();
}catch(Exceptione){
System.err.println("Insideg(),e.printStackTrace()");
e.printStackTrace();
throwe;//17
//throwe.fillInStackTrace();18
}
}
publicstaticvoidmain(String[]args)throwsThrowable{
try{
g();
}catch(Exceptione){
System.err.println(
"Caughtinmain,e.printStackTrace()");
e.printStackTrace();
}
monitor.expect(newString[]{
"originatingtheexceptioninf()",
"Insideg(),e.printStackTrace()",
"java.lang.Exception:thrownfromf()",
"%%/tatRethrowing.f(.*?)",
"%%/tatRethrowing.g(.*?)",
"%%/tatRethrowing.main(.*?)",
"Caughtinmain,e.printStackTrace()",
"java.lang.Exception:thrownfromf()",
"%%/tatRethrowing.f(.*?)",
"%%/tatRethrowing.g(.*?)",
"%%/tatRethrowing.main(.*?)"
});
}
}
Theimportantlinenumbersaremarkedascomments.Withline17uncommented(asshown),theoutputisasshown,sotheexceptionstacktracealwaysremembersitstruepointoforiginnomatterhowmanytimesitgetsrethrown.
最重要的被标识为注释了,而第17行代码我们看到了输出结果,所以这个异常信息始终记得它被抛出的原始位置,不管被抛出了多少次
Withline17commentedandline18uncommented,fillInStackTrace()isusedinstead,andtheresultis:
如果将第17行注释掉而把18行执行,那么fillInStackTrace()会被执行,结果就是:
originatingtheexceptioninf()
Insideg(),e.printStackTrace()
java.lang.Exception:thrownfromf()
atRethrowing.f(Rethrowing.java:9)
atRethrowing.g(Rethrowing.java:12)
atRethrowing.main(Rethrowing.java:23)
Caughtinmain,e.printStackTrace()
java.lang.Exception:thrownfromf()
atRethrowing.g(Rethrowing.java:18)
atRethrowing.main(Rethrowing.java:23)
(PlusadditionalcomplaintsfromtheTest.expect()method.)BecauseoffillInStackTrace(),line18becomesthenewpointoforiginoftheexception.
加上一些Test.expect()方法的错误信息加上finllInStackTrace(),第18行就称为了新的异常的抛出地了。
TheclassThrowablemustappearintheexceptionspecificationforg()andmain()becausefillInStackTrace()producesareferencetoaThrowableobject.SinceThrowableisabaseclassofException,it’spossibletogetanobjectthat’saThrowablebutnotanException,sothehandlerforExceptioninmain()mightmissit.Tomakesureeverythingisinorder,thecompilerforcesanexceptionspecificationforThrowable.Forexample,theexceptioninthefollowingprogramisnotcaughtinmain():
Throwable类必须要出现在g()和main()方法中,因为fillInStackTrace()方法返回了一个Throwable的对象。因为Throwable是Exception的基类,所以在main方法中捕获Exception异常则可能会捕获不到,为了确保程序的有序执行,编译器强制要求在异常说明中使用Throwable。举例来讲,下面的程序中main方法就没有捕获到exception信息。
publicclassThrowOut{
publicstaticvoid
main(String[]args)throwsThrowable{
try{
thrownewThrowable();
}catch(Exceptione){
System.err.println("Caughtinmain()");
}
}
}
It’salsopossibletorethrowadifferentexceptionfromtheoneyoucaught.Ifyoudothis,yougetasimilareffectaswhenyouusefillInStackTrace()—theinformationabouttheoriginalsiteoftheexceptionislost,andwhatyou’releftwithistheinformationpertainingtothenewthrow:
在你捕获异常的地方你可以抛出一个新的异常信息也是可以的。如果你这么做的话,你得到的结果和刚才调用fillInStackTrace()的效果是一样的,原始位置发生异常信息都丢失了,所留下的只是异常再次被抛出时异常的信息了。
importcom.bruceeckel.simpletest.*;
classOneExceptionextendsException{
publicOneException(Strings){super(s);}
}
classTwoExceptionextendsException{
publicTwoException(Strings){super(s);}
}
publicclassRethrowNew{
privatestaticTestmonitor=newTest();
publicstaticvoidf()throwsOneException{
System.out.println("originatingtheexceptioninf()");
thrownewOneException("thrownfromf()");
}
publicstaticvoidmain(String[]args)throwsTwoException{
try{
f();
}catch(OneExceptione){
System.err.println(
"Caughtinmain,e.printStackTrace()");
e.printStackTrace();
thrownewTwoException("frommain()");
}
monitor.expect(newString[]{
"originatingtheexceptioninf()",
"Caughtinmain,e.printStackTrace()",
"OneException:thrownfromf()",
"/tatRethrowNew.f(RethrowNew.java:18)",
"/tatRethrowNew.main(RethrowNew.java:22)",
"Exceptioninthread/"main/""+
"TwoException:frommain()",
"/tatRethrowNew.main(RethrowNew.java:28)"
});
}
}
Thefinalexceptionknowsonlythatitcamefrommain()andnotfromf().
最后的异常对象只是知道这个异常来自main(),而不是f();
Youneverhavetoworryaboutcleaningupthepreviousexception,oranyexceptionsforthatmatter.They’reallheap-basedobjectscreatedwithnew,sothegarbagecollectorautomaticallycleansthemallup.
你不必担心清理这些建立的异常对象,或者其它的异常信息。它们都是通过new方法在堆中创建的,所以垃圾回收器会自动的将它们清理掉。
Exceptionchaining
Oftenyouwanttocatchoneexceptionandthrowanother,butstillkeeptheinformationabouttheoriginatingexception—thisiscalledexceptionchaining.PriortoJDK1.4,programmershadtowritetheirowncodetopreservetheoriginalexceptioninformation,butnowallThrowablesubclassesmaytakeacauseobjectintheirconstructor.Thecauseisintendedtobetheoriginatingexception,andbypassingitinyoumaintainthestacktracebacktoitsorigin,eventhoughyou’recreatingandthrowinganewexceptionatthispoint.往往我们希望捕获了一个异常但是抛出了另外一个异常信息,而且希望记录原始的异常的信息,这叫做“异常链”。早在JDK1.4的时候程序员们就不得不去写代码来保存原始的异常信息,但是现在所有的Throwable的子类的构造方法都能接受一个cause对象,这个cause就是来保存上一个异常的,这样通过你维护的栈轨迹就可用回退到异常原始发生的,即便是你在这个位置创建并且抛出了新的异常也可以。
It’sinterestingtonotethattheonlyThrowablesubclassesthatprovidethecauseargumentintheconstructorarethethreefundamentalexceptionclassesError(usedbytheJVMtoreportsystemerrors),Exception,andRuntimeException.Ifyouwanttochainanyotherexceptiontypes,youdoitthroughtheinitCause()methodratherthantheconstructor.
比较有意思的是Throwable子类中能够支持cause参数构造方法的一共有三个基础异常类,分别是:Error(供JVM报告系统错误使用),Exception和RuntimeException;如果你要链接其它的异常类,那么你只能使用initCause()方法而不能使用构造方法了。
Here’sanexamplethatallowsyoutodynamicallyaddfieldstoaDynamicFieldsobjectatruntime:
下面给出一个例子,允许你运行时动态的在DynamicFields对象中增加数据项;
相关文章推荐
- Error Handling with Exceptions【3】
- Error Handling with exceptions
- Think In Java 笔记8 Error Handling with Exceptions
- Error Handling with Exceptions【5】
- JAVA 复习(Think In Java, 4th) -- Error Handling with Exceptions
- Error Handling with Exceptions(Thinking in Java)
- 第九章: Error Handling with Exceptions
- Error Handling with Exceptions【1】
- Error Handling with C++ Exceptions, Part 1
- Error Handling with Exceptions【4】
- error: exception handling disabled, use -fexceptions to enable
- 解决NDK出现error: exception handling disabled, use -fexceptions to enable的问题
- 【安卓】解决 NDK 出现 error: exception handling disabled, use -fexceptions to enable 的问题
- 解决NDK出现error: exception handling disabled, use -fexceptions to enable的问题
- [JS Compose] 3. Use chain for composable error handling with nested Eithers (flatMap)
- Exceptions and error handling http://yosefk.com/c++fqa/exceptions.html#fqa-17.2
- error: exception handling disabled, use -fexceptions to enable under Android NDK enviroement
- Exception handling with custom error pages in ASP.NET using C#.
- Using R — Easier Error Handling with try()
- error: exception handling disabled, use -fexceptions to enable。