关于Java中finally语句块的深度辨析Word格式.docx
《关于Java中finally语句块的深度辨析Word格式.docx》由会员分享,可在线阅读,更多相关《关于Java中finally语句块的深度辨析Word格式.docx(20页珍藏版)》请在冰点文库上搜索。
![关于Java中finally语句块的深度辨析Word格式.docx](https://file1.bingdoc.com/fileroot1/2023-5/4/5d8903e3-f1b6-43e3-838a-3daf6040eb06/5d8903e3-f1b6-43e3-838a-3daf6040eb061.gif)
}
publicstaticinttest(){
inti=1;
//if(i==1)
//return0;
thepreviousstatementoftryblock"
);
i=i/0;
try{
tryblock"
returni;
}finally{
finallyblock"
清单1的执行结果如下:
thepreviousstatementoftryblock
Exceptioninthread"
main"
java.lang.ArithmeticException:
/byzero
at.bj.charlie.Test.test(Test.java:
15)
at.bj.charlie.Test.main(Test.java:
6)
另外,如果去掉上例中被注释的两条语句前的注释符,执行结果则是:
returnvalueoftest():
0
在以上两种情况下,finally语句块都没有执行,说明什么问题呢?
只有与finally相对应的try语句块得到执行的情况下,finally语句块才会执行。
以上两种情况,都是在try语句块之前返回(return)或者抛出异常,所以try对应的finally语句块没有执行。
那好,即使与finally相对应的try语句块得到执行的情况下,finally语句块一定会执行吗?
不好意思,这次可能又让大家失望了,答案仍然是否定的。
请看下面这个例子(清单2)。
清单2.
System.exit(0);
清单2的执行结果如下:
tryblock
finally语句块还是没有执行,为什么呢?
因为我们在try语句块中执行了System.exit(0)语句,终止了Java虚拟机的运行。
那有人说了,在一般的Java应用中基本上是不会调用这个System.exit(0)方法的。
OK!
没有问题,我们不调用System.exit(0)这个方法,那么finally语句块就一定会执行吗?
再一次让大家失望了,答案还是否定的。
当一个线程在执行try语句块或者catch语句块时被打断(interrupted)或者被终止(killed),与其相对应的finally语句块可能不会执行。
还有更极端的情况,就是在线程运行try语句块或者catch语句块时,突然死机或者断电,finally语句块肯定不会执行了。
可能有人认为死机、断电这些理由有些强词夺理,没有关系,我们只是为了说明这个问题。
回页首
finally语句剖析
说了这么多,还是让我们拿出些有说服力的证据吧!
还有什么证据比官方的文档更具说服力呢?
让我们来看看官方上的《TheJavaTutorials》中是怎样来描述finally语句块的吧!
以下位于****之间的容原封不动的摘自于《TheJavaTutorials》文档。
*******************************************************************************
ThefinallyBlock
Thefinallyblockalwaysexecuteswhenthetryblockexits.Thisensuresthatthefinallyblockisexecutedevenifanunexpectedexceptionoccurs.Butfinallyisusefulformorethanjustexceptionhandling—itallowstheprogrammertoavoidhavingcleanupcodeaccidentallybypassedbyareturn,continue,orbreak.Puttingcleanupcodeinafinallyblockisalwaysagoodpractice,evenwhennoexceptionsareanticipated.
Note:
IftheJVMexitswhilethetryorcatchcodeisbeingexecuted,thenthefinallyblockmaynotexecute.Likewise,ifthethreadexecutingthetryorcatchcodeisinterruptedorkilled,thefinallyblockmaynotexecuteeventhoughtheapplicationasawholecontinues.
请仔细阅读并认真体会一下以上两段英文,当你真正的理解了这两段英文的确切含义,你就可以非常自信的来回答“finally语句块是否一定会执行?
”这样的问题。
看来,大多时候,并不是Java语言本身有多么高深,而是我们忽略了对基础知识的深入理解。
接下来,我们看一下finally语句块是怎样执行的。
在排除了以上finally语句块不执行的情况后,finally语句块就得保证要执行,既然finally语句块一定要执行,那么它和try语句块与catch语句块的执行顺序又是怎样的呢?
还有,如果try语句块中有return语句,那么finally语句块是在return之前执行,还是在return之后执行呢?
带着这样一些问题,我们还是以具体的案例来讲解。
关于try、catch、finally的执行顺序问题,我们还是来看看权威的论述吧!
以下****之间的容摘自Java语言规第四版(《TheJava™ProgrammingLanguage,FourthEdition》)中对于try,catch,和finally的描述。
12.4.Try,catch,andfinally
YoucatchexceptionsbyenclosingcodeinTryblocks.ThebasicsyntaxforaTryblockis:
try{
statements
}catch(exception_type1identifier1){
}catch(exception_type2identifier2){
...
}finally{
}
whereeitheratleastonecatchclause,orthefinallyclause,mustbepresent.Thebodyofthetrystatementisexecuteduntileitheranexceptionisthrownorthebodyfinishessuccessfully.Ifanexceptionisthrown,eachcatchclauseisexaminedinturn,fromfirsttolast,toseewhetherthetypeoftheexceptionobjectisassignabletothetypedeclaredinthecatch.Whenanassignablecatchclauseisfound,itsblockisexecutedwithitsidentifiersettoreferencetheexceptionobject.Noothercatchclausewillbeexecuted.Anynumberofcatchclauses,includingzero,canbeassociatedwithaparticularTRyaslongaseachclausecatchesadifferenttypeofexception.Ifnoappropriatecatchisfound,theexceptionpercolatesoutofthetrystatementintoanyoutertrythatmighthaveacatchclausetohandleit.
Ifafinallyclauseispresentwithatry,itscodeisexecutedafterallotherprocessinginthetryiscomplete.Thishappensnomatterhowcompletionwasachieved,whethernormally,throughanexception,orthroughacontrolflowstatementsuchasreturnorbreak.
上面这段文字的大体意思是说,不管try语句块正常结束还是异常结束,finally语句块是保证要执行的。
如果try语句块正常结束,那么在try语句块中的语句都执行完之后,再执行finally语句块。
如果try中有控制转移语句(return、break、continue)呢?
那finally语句块是在控制转移语句之前执行,还是之后执行呢?
似乎从上面的描述中我们还看不出任何端倪,不要着急,后面的讲解中我们会分析这个问题。
如果try语句块异常结束,应该先去相应的catch块做异常处理,然后执行finally语句块。
同样的问题,如果catch语句块中包含控制转移语句呢?
finally语句块是在这些控制转移语句之前,还是之后执行呢?
我们也会在后续讨论中提到。
其实,关于try,catch,finally的执行流程远非这么简单,有兴趣的读者可以参考Java语言规第三版(《TheJava™LanguageSpecification,ThirdEdition》)中对于Executionoftry-catch-finally的描述,非常复杂的一个流程。
限于篇幅的原因,本文不做摘录,请感兴趣的读者自行阅读。
finally语句示例说明
下面,我们先来看一个简单的例子(清单3)。
清单3.
return;
}finally{
清单3的执行结果为:
finallyblock
清单3说明finally语句块在try语句块中的return语句之前执行。
我们再来看另一个例子(清单4)。
清单4.
returevalueoftest():
publicstaticinttest(){
i=1/0;
return1;
}catch(Exceptione){
exceptionblock"
return2;
清单4的执行结果为:
exceptionblock
returevalueoftest():
2
清单4说明了finally语句块在catch语句块中的return语句之前执行。
从上面的清单3和清单4,我们可以看出,其实finally语句块是在try或者catch中的return语句之前执行的。
更加一般的说法是,finally语句块应该是在控制转移语句之前执行,控制转移语句除了return外,还有break和continue。
另外,throw语句也属于控制转移语句。
虽然return、throw、break和continue都是控制转移语句,但是它们之间是有区别的。
其中return和throw把程序控制权转交给它们的调用者(invoker),而break和continue的控制权是在当前方法转移。
请大家先记住它们的区别,在后续的分析中我们还会谈到。
还是得来点有说服力的证据,下面这段摘自Java语言规第四版(《TheJava™ProgrammingLanguage,FourthEdition》),请读者自己体会一下其含义。
Afinallyclausecanalsobeusedtocleanupforbreak,continue,andreturn,whichisonereasonyouwillsometimesseeatryclausewithnocatchclauses.Whenanycontroltransferstatementisexecuted,allrelevantfinallyclausesareexecuted.Thereisnowaytoleaveatryblockwithoutexecutingitsfinallyclause.
好了,看到这里,是不是有人认为自己已经掌握了finally的用法了?
先别忙着下结论,我们再来看两个例子–清单5和清单6。
清单5.
returnvalueofgetValue():
+getValue());
publicstaticintgetValue(){
return0;
清单5的执行结果:
returnvalueofgetValue():
1
清单6.
i++;
清单6的执行结果:
利用我们上面分析得出的结论:
finally语句块是在try或者catch中的return语句之前执行的。
由此,可以轻松的理解清单5的执行结果是1。
因为finally中的return1;
语句要在try中的return0;
语句之前执行,那么finally中的return1;
语句执行后,把程序的控制权转交给了它的调用者main()函数,并且返回值为1。
那为什么清单6的返回值不是2,而是1呢?
按照清单5的分析逻辑,finally中的i++;
语句应该在try中的returni;
之前执行啊?
i的初始值为1,那么执行i++;
之后为2,再执行returni;
那不就应该是2吗?
怎么变成1了呢?
关于Java虚拟机是如何编译finally语句块的问题,有兴趣的读者可以参考《TheJavaTMVirtualMachineSpecification,SecondEdition》中7.13节Compilingfinally。
那里详细介绍了Java虚拟机是如何编译finally语句块。
实际上,Java虚拟机会把finally语句块作为subroutine(对于这个subroutine不知该如何翻译为好,干脆就不翻译了,免得产生歧义和误解。
)直接插入到try语句块或者catch语句块的控制转移语句之前。
但是,还有另外一个不可忽视的因素,那就是在执行subroutine(也就是finally语句块)之前,try或者catch语句块会保留其返回值到本地变量表(LocalVariableTable)中。
待subroutine执行完毕之后,再恢复保留的返回值到操作数栈中,然后通过return或者throw语句将其返回给该方法的调用者(invoker)。
请注意,前文中我们曾经提到过return、throw和break、continue的区别,对于这条规则(保留返回值),只适用于return和throw语句,不适用于break和continue语句,因为它们根本就没有返回值。
是不是不太好理解,那我们就用具体的例子来做形象的说明吧!
为了能够解释清单6的执行结果,我们来分析一下清单6的字节码(byte-code):
Compiledfrom"
Test.java"
publicclassTestextendsjava.lang.Object{
publicTest();
Code:
0:
aload_0
1:
invokespecial#1;
//Methodjava/lang/Object."
<
init>
"
:
()V
4:
return
LineNumberTable:
line1:
publicstaticvoidmain(java.lang.String[]);
getstatic#2;
//Fieldjava/lang/System.out:
Ljava/io/PrintStream;
3:
new#3;
//classjava/lang/StringBuilder
6:
dup
7:
invokespecial#4;
//Methodjava/lang/StringBuilder."
10:
ldc#5;
//StringreturnvalueofgetValue():
12:
invokevirtual
#6;
//Methodjava/lang/StringBuilder.append:
(
Ljava/lang/String;
)Ljava/lang/StringBuilder;
15:
invokestatic#7;
//Methodg