九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
關(guān)于 Java 中 finally 語句塊的深度辨析

可不能小看這個簡單的 finally,看似簡單的問題背后,卻隱藏了無數(shù)的玄機(jī)。接下來我就帶您一步一步的揭開這個 finally 的神秘面紗。

問題分析

首先來問大家一個問題:finally 語句塊一定會執(zhí)行嗎?

很多人都認(rèn)為 finally 語句塊是肯定要執(zhí)行的,其中也包括一些很有經(jīng)驗(yàn)的 Java 程序員??上Р⒉幌翊蠖嗳怂J(rèn)為的那樣,對于這個問題,答案當(dāng)然是否定的,我們先來看下面這個例子。


清單 1.
				
public class Test {
public static void main(String[] args) {
System. out .println("return value of test(): " + test ());
}

public static int test() {
int i = 1;

// if(i == 1)
// return 0;
System. out .println("the previous statement of try block");
i = i / 0;

try {
System. out .println("try block");
return i;
}finally {
System. out .println("finally block");
}
}
}

清單 1 的執(zhí)行結(jié)果如下:

 the previous statement of try block 
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.bj.charlie.Test.test(Test.java:15)
at com.bj.charlie.Test.main(Test.java:6)

另外,如果去掉上例中被注釋的兩條語句前的注釋符,執(zhí)行結(jié)果則是:

 return value of test(): 0 

在以上兩種情況下,finally 語句塊都沒有執(zhí)行,說明什么問題呢?只有與 finally 相對應(yīng)的 try語句塊得到執(zhí)行的情況下,finally 語句塊才會執(zhí)行。以上兩種情況,都是在 try 語句塊之前返回(return)或者拋出異常,所以 try對應(yīng)的 finally 語句塊沒有執(zhí)行。

那好,即使與 finally 相對應(yīng)的 try 語句塊得到執(zhí)行的情況下,finally 語句塊一定會執(zhí)行嗎?不好意思,這次可能又讓大家失望了,答案仍然是否定的。請看下面這個例子(清單 2)。


清單 2.
				
public class Test {
public static void main(String[] args) {
System. out .println("return value of test(): " + test ());
}

public static int test() {
int i = 1;

try {
System. out .println("try block");
System. exit (0);
return i;
}finally {
System. out .println("finally block");
}
}
}

清單 2 的執(zhí)行結(jié)果如下:

 try block 

finally 語句塊還是沒有執(zhí)行,為什么呢?因?yàn)槲覀冊?try 語句塊中執(zhí)行了 System.exit (0) 語句,終止了Java 虛擬機(jī)的運(yùn)行。那有人說了,在一般的 Java 應(yīng)用中基本上是不會調(diào)用這個 System.exit(0) 方法的。OK!沒有問題,我們不調(diào)用 System.exit(0) 這個方法,那么 finally 語句塊就一定會執(zhí)行嗎?

再一次讓大家失望了,答案還是否定的。當(dāng)一個線程在執(zhí)行 try 語句塊或者 catch語句塊時被打斷(interrupted)或者被終止(killed),與其相對應(yīng)的 finally語句塊可能不會執(zhí)行。還有更極端的情況,就是在線程運(yùn)行 try 語句塊或者 catch 語句塊時,突然死機(jī)或者斷電,finally語句塊肯定不會執(zhí)行了。可能有人認(rèn)為死機(jī)、斷電這些理由有些強(qiáng)詞奪理,沒有關(guān)系,我們只是為了說明這個問題。


回頁首

finally 語句剖析

說了這么多,還是讓我們拿出些有說服力的證據(jù)吧!還有什么證據(jù)比官方的文檔更具說服力呢?讓我們來看看官方網(wǎng)站上的《The Java Tutorials》中是怎樣來描述 finally 語句塊的吧!

以下位于 **** 之間的內(nèi)容原封不動的摘自于《 The Java Tutorials 》文檔。

*******************************************************************************

The finally Block

The finally block always executes when the try block exits.This ensures that the finally block is executed even if an unexpectedexception occurs. But finally is useful for more than just exceptionhandling — it allows the programmer to avoid having cleanup codeaccidentally bypassed by a return, continue, or break. Putting cleanup code in a finally block is always a good practice, even when no exceptions are anticipated.

Note:  If the JVM exits while the try or catch code is being executed,then the finally block may not execute. Likewise, if the threadexecuting the try or catch code is interrupted or killed, the finallyblock may not execute even though the application as a whole continues.

*******************************************************************************

請仔細(xì)閱讀并認(rèn)真體會一下以上兩段英文,當(dāng)你真正的理解了這兩段英文的確切含義,你就可以非常自信的來回答“finally 語句塊是否一定會執(zhí)行?”這樣的問題??磥?,大多時候,并不是 Java 語言本身有多么高深,而是我們忽略了對基礎(chǔ)知識的深入理解。

接下來,我們看一下 finally 語句塊是怎樣執(zhí)行的。在排除了以上 finally 語句塊不執(zhí)行的情況后,finally語句塊就得保證要執(zhí)行,既然 finally 語句塊一定要執(zhí)行,那么它和 try 語句塊與 catch 語句塊的執(zhí)行順序又是怎樣的呢?還有,如果try 語句塊中有 return 語句,那么 finally 語句塊是在 return 之前執(zhí)行,還是在 return之后執(zhí)行呢?帶著這樣一些問題,我們還是以具體的案例來講解。

關(guān)于 try、catch、finally 的執(zhí)行順序問題,我們還是來看看權(quán)威的論述吧!以下 **** 之間的內(nèi)容摘自 Java 語言規(guī)范第四版( The Java™ Programming Language, Fourth Edition )中對于 try,catch,和 finally 的描述。

*******************************************************************************

12.4. Try, catch, and finally

You catch exceptions by enclosing code in Try blocks. The basic syntax for a Try block is:

try {

statements

} catch (exception_type1 identifier1) {

statements

} catch (exception_type2 identifier2) {

statements

...

} finally {

statements

}

where either at least one catch clause, or the finally clause,must be present. The body of the try statement is executed until eitheran exception is thrown or the body finishes successfully. If anexception is thrown, each catch clause is examined in turn, from firstto last, to see whether the type of the exception object is assignableto the type declared in the catch. When an assignable catch clause isfound, its block is executed with its identifier set to reference theexception object. No other catch clause will be executed. Any number ofcatch clauses, including zero, can be associated with a particular TRyas long as each clause catches a different type of exception. If noappropriate catch is found, the exception percolates out of the trystatement into any outer try that might have a catch clause to handleit.

If a finally clause is present with a try, its code isexecuted after all other processing in the try is complete. This happensno matter how completion was achieved, whether normally, through anexception, or through a control flow statement such as return or break .

*******************************************************************************

上面這段文字的大體意思是說,不管 try 語句塊正常結(jié)束還是異常結(jié)束,finally 語句塊是保證要執(zhí)行的。如果 try語句塊正常結(jié)束,那么在 try 語句塊中的語句都執(zhí)行完之后,再執(zhí)行 finally 語句塊。如果 try中有控制轉(zhuǎn)移語句(return、break、continue)呢?那 finally語句塊是在控制轉(zhuǎn)移語句之前執(zhí)行,還是之后執(zhí)行呢?似乎從上面的描述中我們還看不出任何端倪,不要著急,后面的講解中我們會分析這個問題。如果 try語句塊異常結(jié)束,應(yīng)該先去相應(yīng)的 catch 塊做異常處理,然后執(zhí)行 finally 語句塊。同樣的問題,如果 catch語句塊中包含控制轉(zhuǎn)移語句呢? finally 語句塊是在這些控制轉(zhuǎn)移語句之前,還是之后執(zhí)行呢?我們也會在后續(xù)討論中提到。

其實(shí),關(guān)于 try,catch,finally 的執(zhí)行流程遠(yuǎn)非這么簡單,有興趣的讀者可以參考 Java 語言規(guī)范第三版( The Java™ Language Specification, Third Edition )中對于 Execution of try-catch-finally 的描述,非常復(fù)雜的一個流程。限于篇幅的原因,本文不做摘錄,請感興趣的讀者自行閱讀。


回頁首

finally 語句示例說明

下面,我們先來看一個簡單的例子(清單 3)。


清單 3.
				
public class Test {
public static void main(String[] args) {
try {
System. out .println("try block");

return ;
} finally {
System. out .println("finally block");
}
}
}

清單 3 的執(zhí)行結(jié)果為:

 try block 
finally block

清單 3 說明 finally 語句塊在 try 語句塊中的 return 語句之前執(zhí)行。我們再來看另一個例子(清單 4)。


清單 4.
				
public class Test {
public static void main(String[] args) {
System. out .println("reture value of test() : " + test ());
}

public static int test(){
int i = 1;

try {
System. out .println("try block");
i = 1 / 0;
return 1;
}catch (Exception e){
System. out .println("exception block");
return 2;
}finally {
System. out .println("finally block");
}
}
}

清單 4 的執(zhí)行結(jié)果為:

 try block 
exception block
finally block
reture value of test() : 2

清單 4 說明了 finally 語句塊在 catch 語句塊中的 return 語句之前執(zhí)行。

從上面的清單 3 和清單 4,我們可以看出,其實(shí) finally 語句塊是在 try 或者 catch 中的 return語句之前執(zhí)行的。更加一般的說法是,finally 語句塊應(yīng)該是在控制轉(zhuǎn)移語句之前執(zhí)行,控制轉(zhuǎn)移語句除了 return 外,還有 break 和continue。另外,throw 語句也屬于控制轉(zhuǎn)移語句。雖然 return、throw、break 和 continue都是控制轉(zhuǎn)移語句,但是它們之間是有區(qū)別的。其中 return 和 throw 把程序控制權(quán)轉(zhuǎn)交給它們的調(diào)用者(invoker),而 break 和continue 的控制權(quán)是在當(dāng)前方法內(nèi)轉(zhuǎn)移。請大家先記住它們的區(qū)別,在后續(xù)的分析中我們還會談到。

還是得來點(diǎn)有說服力的證據(jù),下面這段摘自 Java 語言規(guī)范第四版( The Java™ Programming Language, Fourth Edition ),請讀者自己體會一下其含義。

*******************************************************************************

A finally clause can also be used to clean up for break , continue , and return , which is one reason you will sometimes see a try clause with no catch clauses. When any control transfer statement is executed, all relevant finally clauses are executed. There is no way to leave a try block without executing its finally clause.

*******************************************************************************

好了,看到這里,是不是有人認(rèn)為自己已經(jīng)掌握了 finally 的用法了?先別忙著下結(jié)論,我們再來看兩個例子 – 清單 5 和清單 6。


清單 5.
				
public class Test {
public static void main(String[] args) {
System. out .println("return value of getValue(): " + getValue ());
}

public static int getValue() {
try {
return 0;
} finally {
return 1;
}
}
}

清單 5 的執(zhí)行結(jié)果:

 return value of getValue(): 1 


清單 6.
				
public class Test {
public static void main(String[] args) {
System. out .println("return value of getValue(): " + getValue ());
}

public static int getValue() {
int i = 1;
try {
return i;
} finally {
i++;
}
}
}

清單 6 的執(zhí)行結(jié)果:

 return value of getValue(): 1 

利用我們上面分析得出的結(jié)論:finally 語句塊是在 try 或者 catch 中的 return 語句之前執(zhí)行的。由此,可以輕松的理解清單 5 的執(zhí)行結(jié)果是 1。因?yàn)?finally 中的 return 1;語句要在 try 中的 return0;語句之前執(zhí)行,那么 finally 中的 return 1;語句執(zhí)行后,把程序的控制權(quán)轉(zhuǎn)交給了它的調(diào)用者 main()函數(shù),并且返回值為1。那為什么清單 6 的返回值不是 2,而是 1 呢?按照清單 5 的分析邏輯,finally 中的 i++;語句應(yīng)該在 try 中的return i;之前執(zhí)行??? i 的初始值為 1,那么執(zhí)行 i++;之后為 2,再執(zhí)行 return i;那不就應(yīng)該是 2 嗎?怎么變成 1了呢?

關(guān)于 Java 虛擬機(jī)是如何編譯 finally 語句塊的問題,有興趣的讀者可以參考《 The JavaTM VirtualMachine Specification, Second Edition 》中 7.13 節(jié) Compilingfinally。那里詳細(xì)介紹了 Java 虛擬機(jī)是如何編譯 finally 語句塊。實(shí)際上,Java 虛擬機(jī)會把 finally 語句塊作為subroutine(對于這個 subroutine 不知該如何翻譯為好,干脆就不翻譯了,免得產(chǎn)生歧義和誤解。)直接插入到 try 語句塊或者catch 語句塊的控制轉(zhuǎn)移語句之前。但是,還有另外一個不可忽視的因素,那就是在執(zhí)行 subroutine(也就是 finally語句塊)之前,try 或者 catch 語句塊會保留其返回值到本地變量表(Local Variable Table)中。待 subroutine執(zhí)行完畢之后,再恢復(fù)保留的返回值到操作數(shù)棧中,然后通過 return 或者 throw語句將其返回給該方法的調(diào)用者(invoker)。請注意,前文中我們曾經(jīng)提到過 return、throw 和 break、continue的區(qū)別,對于這條規(guī)則(保留返回值),只適用于 return 和 throw 語句,不適用于 break 和 continue語句,因?yàn)樗鼈兏揪蜎]有返回值。

是不是不太好理解,那我們就用具體的例子來做形象的說明吧!

為了能夠解釋清單 6 的執(zhí)行結(jié)果,我們來分析一下清單 6 的字節(jié)碼(byte-code):

 Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
Code:
0: aload_0
1:invokespecial#1; //Method java/lang/Object."<init>":()V
4: return

LineNumberTable:
line 1: 0

public static void main(java.lang.String[]);
Code:
0: getstatic #2; //Field java/lang/System.out:Ljava/io/PrintStream;
3: new #3; //class java/lang/StringBuilder
6: dup
7: invokespecial #4; //Method java/lang/StringBuilder."<init>":()V
10: ldc #5; //String return value of getValue():
12: invokevirtual
#6; //Method java/lang/StringBuilder.append:(
Ljava/lang/String;)Ljava/lang/StringBuilder;
15: invokestatic #7; //Method getValue:()I
18: invokevirtual
#8; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
21: invokevirtual
#9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: invokevirtual #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
27: return

public static int getValue();
Code:
0: iconst_1
1: istore_0
2: iload_0
3: istore_1
4: iinc 0, 1
7: iload_1
8: ireturn
9: astore_2
10: iinc 0, 1
13: aload_2
14: athrow
Exception table:
from to target type
2 4 9 any
9 10 9 any
}

對于 Test()構(gòu)造方法與 main()方法,在這里,我們不做過多解釋。讓我們來分析一下 getValue()方法的執(zhí)行。在這之前,先讓我把 getValue()中用到的虛擬機(jī)指令解釋一下,以便讀者能夠正確的理解該函數(shù)的執(zhí)行。

1.	iconst_ 
Description: Push the int constant (-1, 0, 1, 2, 3, 4 or 5) onto the operand stack.
Forms: iconst_m1 = 2 (0x2) iconst_0 = 3 (0x3) iconst_1 = 4 (0x4)
iconst_2 = 5 (0x5) iconst_3 = 6 (0x6) iconst_4 = 7 (0x7) iconst_5 = 8 (0x8)

2. istore_
Description: Store int into local variable. The must be an index into the
local variable array of the current frame.
Forms: istore_0 = 59 (0x3b) istore_1 = 60 (0x3c) istore_2 = 61 (0x3d)
istore_3 = 62 (0x3e)

3. iload_
Description: Load int from local variable. The must be an index into the
local variable array of the current frame.
Forms: iload_0 = 26 (0x1a) iload_1 = 27 (0x1b) iload_2 = 28 (0x1c) iload_3 = 29 (0x1d)

4. iinc index, const
Description: Increment local variable by constant. The index is an unsigned byte that
must be an index into the local variable array of the current frame. The const is an
immediate signed byte. The local variable at index must contain an int. The value
const is first sign-extended to an int, and then the local variable at index is
incremented by that amount.
Forms: iinc = 132 (0x84)

Format:
iinc
index
const

5. ireturn
Description: Return int from method.
Forms: ireturn = 172 (0xac)

6. astore_
Description: Store reference into local variable. The must be an index into the
local variable array of the current frame.
Forms: astore_0 = 75 (0x4b) astore_1 = 76 (0x4c) astore_2 =77 (0x4d) astore_3 =78 (0x4e)

7. aload_
Description: Load reference from local variable. The must be an index into the
local variable array of the current frame.
Forms: aload_0 = 42 (0x2a) aload_1 = 43 (0x2b) aload_2 = 44 (0x2c) aload_3 = 45 (0x2d)

8. athrow
Description: Throw exception or error.
Forms: athrow = 191 (0xbf)



有了以上的 Java 虛擬機(jī)指令,我們來分析一下其執(zhí)行順序:分為正常執(zhí)行(沒有 exception)和異常執(zhí)行(有 exception)兩種情況。我們先來看一下正常執(zhí)行的情況,如圖 1 所示:


圖 1. getValue()函數(shù)正常執(zhí)行的情況

由上圖,我們可以清晰的看出,在 finally 語句塊(iinc 0,1)執(zhí)行之前,getValue()方法保存了其返回值(1)到本地表量表中 1 的位置,完成這個任務(wù)的指令是 istore_1;然后執(zhí)行finally 語句塊(iinc 0, 1),finally 語句塊把位于 0 這個位置的本地變量表中的值加 1,變成 2;待 finally語句塊執(zhí)行完畢之后,把本地表量表中 1 的位置上值恢復(fù)到操作數(shù)棧(iload_1),最后執(zhí)行 ireturn指令把當(dāng)前操作數(shù)棧中的值(1)返回給其調(diào)用者(main)。這就是為什么清單 6 的執(zhí)行結(jié)果是 1,而不是 2 的原因。

再讓我們來看看異常執(zhí)行的情況。是不是有人會問,你的清單 6 中都沒有 catch語句,哪來的異常處理呢?我覺得這是一個好問題,其實(shí),即使沒有 catch 語句,Java編譯器編譯出的字節(jié)碼中還是有默認(rèn)的異常處理的,別忘了,除了需要捕獲的異常,還可能有不需捕獲的異常(如:RunTimeException 和Error)。

從 getValue()方法的字節(jié)碼中,我們可以看到它的異常處理表(exception table), 如下:

Exception table:

from to target type

2 4 9 any

它的意思是說:如果從 2 到 4 這段指令出現(xiàn)異常,則由從 9 開始的指令來處理。


圖 2. getValue()函數(shù)異常執(zhí)行的情況

先說明一點(diǎn),上圖中的 exception 其實(shí)應(yīng)該是 exception 對象的引用,為了方便說明,我直接把它寫成 exception 了。

由上圖(圖 2)可知,當(dāng)從 2 到 4 這段指令出現(xiàn)異常時,將會產(chǎn)生一個 exception對象,并且把它壓入當(dāng)前操作數(shù)棧的棧頂。接下來是 astore_2 這條指令,它負(fù)責(zé)把 exception 對象保存到本地變量表中 2的位置,然后執(zhí)行 finally 語句塊,待 finally 語句塊執(zhí)行完畢后,再由 aload_2 這條指令把預(yù)先存儲的 exception對象恢復(fù)到操作數(shù)棧中,最后由 athrow 指令將其返回給該方法的調(diào)用者(main)。

通過以上的分析,大家應(yīng)該已經(jīng)清楚 try-catch-finally 語句塊的執(zhí)行流程了吧!

為了更具說服力,我們還是來引經(jīng)據(jù)典吧!大家可以不相信我,難道還不相信“高司令”(Gosling)嗎?下面這段仍然摘自 Java 語言規(guī)范第四版 The Java™ Programming Language, Fourth Edition ,請讀者自己體會吧!

*******************************************************************************

a finally clause is always entered with a reason. That reasonmay be that the try code finished normally, that it executed a controlflow statement such as return, or that an exception was thrown in codeexecuted in the Try block. The reason is remembered when the finallyclause exits by falling out the bottom. However, if the finally blockcreates its own reason to leave by executing a control flow statement(such as break or return) or by throwing an exception, that reasonsupersedes the original one, and the original reason is forgotten. Forexample, consider the following code:
try {
// … do something …
return 1;
} finally {
return 2;
}
Whenthe Try block executes its return, the finally block is entered withthe “reason” of returning the value 1. However, inside the finally blockthe value 2 is returned, so the initial intention is forgotten. Infact, if any of the other code in the try block had thrown an exception,the result would still be to return 2. If the finally block did notreturn a value but simply fell out the bottom, the “return the value 1 ″reason would be remembered and carried out.

*******************************************************************************

好了,有了以上的知識,讓我們再來看以下 3 個例子。


清單 7.
				
public class Test {
public static void main(String[] args) {
System. out .println("return value of getValue(): " + getValue ());
}

@SuppressWarnings("finally")
public static int getValue() {
int i = 1;
try {
i = 4;
} finally {
i++;
return i;
}
}
}

清單 7 的執(zhí)行結(jié)果:

 return value of getValue(): 5 


清單 8.
				
public class Test {
public static void main(String[] args) {
System. out .println("return value of getValue(): " + getValue ());
}

public static int getValue() {
int i = 1;
try {
i = 4;
} finally {
i++;
}

return i;
}
}

清單 8 的執(zhí)行結(jié)果:

 return value of getValue(): 5 

清單 7 和清單 8 應(yīng)該還比較簡單吧!利用我們上面講解的知識,很容易分析出其結(jié)果。讓我們再來看一個稍微復(fù)雜一點(diǎn)的例子 – 清單 9。我建議大家最好先不要看執(zhí)行結(jié)果,運(yùn)用學(xué)過的知識來分析一下,看是否能推斷出正確的結(jié)果。


清單 9.
				
public class Test {
public static void main(String[] args) {
System. out .println( test ());
}

public static String test() {
try {
System. out .println("try block");
return test1 ();
} finally {
System. out .println("finally block");
}
}
public static String test1() {
System. out .println("return statement");
return "after return";
}
}

清單 9 的結(jié)果:

 try block 
return statement
finally block
after return

你分析對了嗎?其實(shí)這個案例也不算很難,return test1();這條語句等同于 :

 String tmp = test1(); 
return tmp;

這樣,就應(yīng)該清楚為什么是上面所示的執(zhí)行結(jié)果了吧!

好了,就寫到這吧!希望大家看完這篇文章能夠有所收獲!


回頁首

總結(jié)

沒想到吧!一個小小的、看似簡單的 finally 語句塊背后居然隱藏了這么多玄機(jī)??磥恚覀兤綍r還是應(yīng)該認(rèn)真的閱讀 Java相關(guān)的基礎(chǔ)文檔,比如:Java 語言規(guī)范、Java虛擬機(jī)規(guī)范等,很多棘手的問題都可以從中得到答案。只有真正的吃透了基礎(chǔ)知識,才能達(dá)到運(yùn)用自如的境界!


本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
java try、catch、finally及finally執(zhí)行順序詳解
關(guān)于java異常處理機(jī)制的深入理解
Java異常處理習(xí)題
Java中try catch finally語句中含有return語句的執(zhí)行情況(總結(jié)版)...
Java finally 語句到底是在 return 之前還是之后執(zhí)行?
java 異常捕捉 ( try catch finally ) 你真的掌握了嗎?
更多類似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服