程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 完整解析Java編程中finally語句的履行道理

完整解析Java編程中finally語句的履行道理

編輯:關於JAVA

完整解析Java編程中finally語句的履行道理。本站提示廣大學習愛好者:(完整解析Java編程中finally語句的履行道理)文章只能為提供參考,不一定能成為您想要的結果。以下是完整解析Java編程中finally語句的履行道理正文


可不克不及小視這個簡略的 finally,看似簡略的成績面前,卻隱蔽了有數的玄機。接上去我就帶您一步一步的揭開這個 finally 的奧秘面紗。
成績剖析
起首來問年夜家一個成績:finally 語句塊必定會履行嗎?
許多人都以為 finally 語句塊是確定要履行的,個中也包含一些很有經歷的 Java 法式員。惋惜其實不像年夜多人所以為的那樣,關於這個成績,謎底固然能否定的,我們先來看上面這個例子。
清單 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 的履行成果以下:

 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)

別的,假如去失落上例中被正文的兩條語句前的正文符,履行成果則是:

 return value of test(): 0

在以上兩種情形下,finally 語句塊都沒有履行,解釋甚麼成績呢?只要與 finally 絕對應的 try 語句塊獲得履行的情形下,finally 語句塊才會履行。以上兩種情形,都是在 try 語句塊之前前往(return)或許拋出異常,所以 try 對應的 finally 語句塊沒有履行。
那好,即便與 finally 絕對應的 try 語句塊獲得履行的情形下,finally 語句塊必定會履行嗎?欠好意思,此次能夠又讓年夜家掉望了,謎底依然能否定的。請看上面這個例子(清單 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 的履行成果以下:

 try block

finally 語句塊照樣沒有履行,為何呢?由於我們在 try 語句塊中履行了 System.exit (0) 語句,終止了 Java 虛擬機的運轉。那有人說了,在普通的 Java 運用中根本上是不會挪用這個 System.exit(0) 辦法的。OK !沒有成績,我們不挪用 System.exit(0) 這個辦法,那末 finally 語句塊就必定會履行嗎?
再一次讓年夜家掉望了,謎底照樣否認的。當一個線程在履行 try 語句塊或許 catch 語句塊時被打斷(interrupted)或許被終止(killed),與其絕對應的 finally 語句塊能夠不會履行。還有更極真個情形,就是在線程運轉 try 語句塊或許 catch 語句塊時,忽然逝世機或許斷電,finally 語句塊確定不會履行了。能夠有人以為逝世機、斷電這些來由有些蠻橫無理,沒有關系,我們只是為了解釋這個成績。

finally 語句分析
說了這麼多,照樣讓我們拿出些有壓服力的證據吧!還有甚麼證據比官方的文檔更具壓服力呢?讓我們來看看官方網站上的《The Java Tutorials》中是如何來描寫 finally 語句塊的吧!
以下位於 **** 之間的內容原封不動的摘自於《 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 unexpected exception occurs. But finally is useful for more than just exception handling — it allows the programmer to avoid having cleanup code accidentally 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 thread executing the try or catch code is interrupted or killed, the finally block may not execute even though the application as a whole continues.
*******************************************************************************
請細心浏覽並賣力領會一下以上兩段英文,當你真實的懂得了這兩段英文切實其實切寄義,你便可以異常自負的往返答“finally 語句塊能否必定會履行?”如許的成績。看來,年夜多時刻,其實不是 Java 說話自己有何等精深,而是我們疏忽了對基本常識的深刻懂得。
接上去,我們看一下 finally 語句塊是如何履行的。在消除了以上 finally 語句塊不履行的情形後,finally 語句塊就得包管要履行,既然 finally 語句塊必定要履行,那末它和 try 語句塊與 catch 語句塊的履行次序又是如何的呢?還有,假如 try 語句塊中有 return 語句,那末 finally 語句塊是在 return 之前履行,照樣在 return 以後履行呢?帶著如許一些成績,我們照樣以詳細的案例來說解。
關於 try、catch、finally 的履行次序成績,我們照樣來看看威望的闡述吧!以下 **** 之間的內容摘自 Java 說話標准第四版(《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 either an exception is thrown or the body finishes successfully. If an exception is thrown, each catch clause is examined in turn, from first to last, to see whether the type of the exception object is assignable to the type declared in the catch. When an assignable catch clause is found, its block is executed with its identifier set to reference the exception object. No other catch clause will be executed. Any number of catch clauses, including zero, can be associated with a particular TRy as long as each clause catches a different type of exception. If no appropriate catch is found, the exception percolates out of the try statement into any outer try that might have a catch clause to handle it.
If a finally clause is present with a try, its code is executed after all other processing in the try is complete. This happens no matter how completion was achieved, whether normally, through an exception, or through a control flow statement such as return or break.
*******************************************************************************
下面這段文字的年夜體意思是說,不論 try 語句塊正常停止照樣異常停止,finally 語句塊是包管要履行的。假如 try 語句塊正常停止,那末在 try 語句塊中的語句都履行完以後,再履行 finally 語句塊。假如 try 中有掌握轉移語句(return、break、continue)呢?那 finally 語句塊是在掌握轉移語句之前履行,照樣以後履行呢?仿佛從下面的描寫中我們還看不出任何眉目,不要焦急,前面的講授中我們會剖析這個成績。假如 try 語句塊異常停止,應當先去響應的 catch 塊做異常處置,然後履行 finally 語句塊。異樣的成績,假如 catch 語句塊中包括掌握轉移語句呢? finally 語句塊是在這些掌握轉移語句之前,照樣以後履行呢?我們也會在後續評論辯論中提到。
其實,關於 try,catch,finally 的履行流程遠非這麼簡略,有興致的讀者可以參考 Java 說話標准第三版(《The Java™ Language Specification, Third Edition》)中關於 Execution of try-catch-finally 的描寫,異常龐雜的一個流程。限於篇幅的緣由,本文不做摘錄,請感興致的讀者自行浏覽。

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 的履行成果為:

 try block 
 finally block

清單 3 解釋 finally 語句塊在 try 語句塊中的 return 語句之前履行。我們再來看另外一個例子(清單 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 的履行成果為:

 try block 
 exception block 
 finally block 
 reture value of test() : 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 說話標准第四版(《The Java™ Programming Language, Fourth Edition》),請讀者本身領會一下其寄義。
*******************************************************************************
Afinallyclause can also be used to clean up forbreak,continue, andreturn, which is one reason you will sometimes see atryclause with nocatchclauses. When any control transfer statement is executed, all relevantfinallyclauses are executed. There is no way to leave atryblock without executing itsfinallyclause.
*******************************************************************************
好了,看到這裡,是否是有人以為本身曾經控制了 finally 的用法了?先別忙著下結論,我們再來看兩個例子 – 清單 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 的履行成果:

 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 的履行成果:

 return value of getValue(): 1

應用我們下面剖析得出的結論:finally 語句塊是在 try 或許 catch 中的 return 語句之前履行的。 由此,可以輕松的懂得清單 5 的履行成果是 1。由於 finally 中的 return 1;語句要在 try 中的 return 0;語句之前履行,那末 finally 中的 return 1;語句履行後,把法式的掌握權轉交給了它的挪用者 main()函數,而且前往值為 1。那為何清單 6 的前往值不是 2,而是 1 呢?依照清單 5 的剖析邏輯,finally 中的 i++;語句應當在 try 中的 return i;之前履行啊? i 的初始值為 1,那末履行 i++;以後為 2,再履行 return i;那不就應當是 2 嗎?怎樣釀成 1 了呢?
關於 Java 虛擬機是若何編譯 finally 語句塊的成績,有興致的讀者可以參考《 The JavaTM Virtual Machine Specification, Second Edition 》中 7.13 節 Compiling finally。那邊具體引見了 Java 虛擬機是若何編譯 finally 語句塊。現實上,Java 虛擬機遇把 finally 語句塊作為 subroutine(關於這個 subroutine 不知該若何翻譯為好,爽性就不翻譯了,省得發生歧義和誤會。)直接拔出到 try 語句塊或許 catch 語句塊的掌握轉移語句之前。然則,還有別的一個弗成疏忽的身分,那就是在履行 subroutine(也就是 finally 語句塊)之前,try 或許 catch 語句塊會保存其前往值到當地變量表(Local Variable Table)中。待 subroutine 履行終了以後,再恢復保存的前往值到操作數棧中,然後經由過程 return 或許 throw 語句將其前往給該辦法的挪用者(invoker)。請留意,前文中我們已經提到過 return、throw 和 break、continue 的差別,關於這條規矩(保存前往值),只實用於 return 和 throw 語句,不實用於 break 和 continue 語句,由於它們基本就沒有前往值。
是否是不太好懂得,那我們就器具體的例子來做抽象的解釋吧!
為了可以或許說明清單 6 的履行成果,我們來剖析一下清單 6 的字節碼(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()結構辦法與 main()辦法,在這裡,我們不做過量說明。讓我們來剖析一下 getValue()辦法的履行。在這之前,先讓我把 getValue()頂用到的虛擬機指令說明一下,以便讀者可以或許准確的懂得該函數的履行。
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 虛擬機指令,我們來剖析一下其履行次序:分為正常履行(沒有 exception)和異常履行(有 exception)兩種情形。我們先來看一下正常履行的情形,如圖 1 所示:
圖 1. getValue()函數正常履行的情形

由上圖,我們可以清楚的看出,在 finally 語句塊(iinc 0, 1)履行之前,getValue()辦法保留了其前往值(1)到當地表量表中 1 的地位,完成這個義務的指令是 istore_1;然後履行 finally 語句塊(iinc 0, 1),finally 語句塊把位於 0 這個地位的當地變量表中的值加 1,釀成 2;待 finally 語句塊履行終了以後,把當地表量表中 1 的地位上值恢復到操作數棧(iload_1),最初履行 ireturn 指令把以後操作數棧中的值(1)前往給其挪用者(main)。這就是為何清單 6 的履行成果是 1,而不是 2 的緣由。
再讓我們來看看異常履行的情形。是否是有人會問,你的清單 6 中都沒有 catch 語句,哪來的異常處置呢?我認為這是一個好成績,其實,即便沒有 catch 語句,Java 編譯器編譯出的字節碼中照樣有默許的異常處置的,別忘了,除須要捕捉的異常,還能夠有不需捕捉的異常(如:RunTimeException 和 Error)。
從 getValue()辦法的字節碼中,我們可以看到它的異常處置表(exception table), 以下:

Exception table:
from to target type
2 4 9 any

它的意思是說:假如從 2 到 4 這段指令湧現異常,則由從 9 開端的指令來處置。
圖 2. getValue()函數異常履行的情形

先解釋一點,上圖中的 exception 其實應當是 exception 對象的援用,為了便利解釋,我直接把它寫成 exception 了。
由上圖(圖 2)可知,當從 2 到 4 這段指令湧現異常時,將會發生一個 exception 對象,而且把它壓入以後操作數棧的棧頂。接上去是 astore_2 這條指令,它擔任把 exception 對象保留到當地變量表中 2 的地位,然後履行 finally 語句塊,待 finally 語句塊履行終了後,再由 aload_2 這條指令把事後存儲的 exception 對象恢復到操作數棧中,最初由 athrow 指令將其前往給該辦法的挪用者(main)。
經由過程以上的剖析,年夜家應當曾經清晰 try-catch-finally 語句塊的履行流程了吧!
為了更具壓服力,我們照樣來旁征博引吧!年夜家可以不信任我,豈非還不信任“高司令”(Gosling)嗎?上面這段依然摘自 Java 說話標准第四版 《The Java™ Programming Language, Fourth Edition》,請讀者本身領會吧!
*******************************************************************************
a finally clause is always entered with a reason. That reason may be that the try code finished normally, that it executed a control flow statement such as return, or that an exception was thrown in code executed in the Try block. The reason is remembered when the finally clause exits by falling out the bottom. However, if the finally block creates its own reason to leave by executing a control flow statement (such as break or return) or by throwing an exception, that reason supersedes the original one, and the original reason is forgotten. For example, consider the following code:
try {
// … do something …
return 1;
} finally {
return 2;
}
When the Try block executes its return, the finally block is entered with the “reason” of returning the value 1. However, inside the finally block the value 2 is returned, so the initial intention is forgotten. In fact, 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 not return 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 的履行成果:

 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 的履行成果:

 return value of getValue(): 5

清單 7 和清單 8 應當還比擬簡略吧!應用我們下面講授的常識,很輕易剖析出其成果。讓我們再來看一個略微龐雜一點的例子 – 清單 9。我建議年夜家最好先不要看履行成果,應用學過的常識來剖析一下,看能否能揣摸出准確的成果。
清單 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 的成果:

 try block 
 return statement 
 finally block 
 after return

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

 String tmp = test1(); 
 return tmp;

如許,就應當清晰為何是下面所示的履行成果了吧!
好了,就寫到這吧!願望年夜家看完這篇文章可以或許有所收成!

總結
沒想到吧!一個小小的、看似簡略的 finally 語句塊面前竟然隱蔽了這麼多玄機。看來,我們日常平凡照樣應當賣力的浏覽 Java 相干的基本文檔,好比:Java 說話標准、Java 虛擬機標准等,許多辣手的成績都可以從中獲得謎底。只要真實的吃透了基本常識,能力到達應用自若的境地!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved