隨筆 - 63  文章 - 0  trackbacks - 0
          <2009年5月>
          262728293012
          3456789
          10111213141516
          17181920212223
          24252627282930
          31123456

          常用鏈接

          留言簿(2)

          隨筆分類

          隨筆檔案

          搜索

          •  

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          1 引子
          try…catch…finally恐怕是大家再熟悉不過的語句了,而且感覺用起來也是很簡單,邏輯上似乎也是很容易理解。不過,我親自體驗(yàn)的“教訓(xùn)”告訴我,這個(gè)東西可不是想象中的那么簡單、聽話。不信?那你看看下面的代碼,“猜猜”它執(zhí)行后的結(jié)果會(huì)是什么?不要往后看答案、也不許執(zhí)行代碼看真正答案哦。如果你的答案是正確,那么這篇文章你就不用浪費(fèi)時(shí)間看啦。
          public class TestException
          {
              public TestException()
              {
              }
              boolean testEx() throws Exception
              {
                  boolean ret = true;
                  try
                  {
                      ret = testEx1();
                  }
                  catch (Exception e)
                  {
                      System.out.println("testEx, catch exception");
                      ret = false;
                      throw e;
                  }
                  finally
                  {
                      System.out.println("testEx, finally; return value=" + ret);
                      return ret;
                  }
              }
              boolean testEx1() throws Exception
              {
                  boolean ret = true;
                  try
                  {
                      ret = testEx2();
                      if (!ret)
                      {
                          return false;
                      }
                      System.out.println("testEx1, at the end of try");
                      return ret;
                  }
                  catch (Exception e)
                  {
                      System.out.println("testEx1, catch exception");
                      ret = false;
                      throw e;
                  }
                  finally
                  {
                      System.out.println("testEx1, finally; return value=" + ret);
                      return ret;
                  }
              }
              boolean testEx2() throws Exception
              {
                  boolean ret = true;
                  try
                  {
                      int b = 12;
                      int c;
                      for (int i = 2; i >= -2; i--)
                      {
                          c = b / i;
                          System.out.println("i=" + i);
                      }
                      return true;
                  }
                  catch (Exception e)
                  {
                      System.out.println("testEx2, catch exception");
                      ret = false;
                      throw e;
                  }
                  finally
                  {
                      System.out.println("testEx2, finally; return value=" + ret);
                      return ret;
                  }
              }
              public static void main(String[] args)
              {
                  TestException testException1 = new TestException();
                  try
                  {
                      testException1.testEx();
                  }
                  catch (Exception e)
                  {
                      e.printStackTrace();
                  }
              }
          }
          你的答案是什么?是下面的答案嗎?
          i=2
          i=1
          testEx2, catch exception
          testEx2, finally; return value=false
          testEx1, catch exception
          testEx1, finally; return value=false
          testEx, catch exception
          testEx, finally; return value=false
          如果你的答案真的如上面所說,那么你錯(cuò)啦。^_^,那就建議你仔細(xì)看一看這篇文章或者拿上面的代碼按各種不同的情況修改、執(zhí)行、測試,你會(huì)發(fā)現(xiàn)有很多事情不是原來想象中的那么簡單的。
          現(xiàn)在公布正確答案:
          i=2
          i=1
          testEx2, catch exception
          testEx2, finally; return value=false
          testEx1, finally; return value=false
          testEx, finally; return value=false

          2 基礎(chǔ)知識(shí)

          2.1 相關(guān)概念
          例外是在程序運(yùn)行過程中發(fā)生的異常事件,比如除0溢出、數(shù)組越界、文件找不到等,這些事件的發(fā)生將阻止程序的正常運(yùn)行。為了加強(qiáng)程序的魯棒性,程序設(shè)計(jì)時(shí),必須考慮到可能發(fā)生的異常事件并做出相應(yīng)的處理。C語言中,通過使用if語句來判斷是否出現(xiàn)了例外,同時(shí),調(diào)用函數(shù)通過被調(diào)用函數(shù)的返回值感知在被調(diào)用函數(shù)中產(chǎn)生的例外事件并進(jìn)行處理。全程變量ErroNo常常用來反映一個(gè)異常事件的類型。但是,這種錯(cuò)誤處理機(jī)制會(huì)導(dǎo)致不少問題。
          Java通過面向?qū)ο蟮姆椒▉硖幚砝狻T谝粋€(gè)方法的運(yùn)行過程中,如果發(fā)生了例外,則這個(gè)方法生成代表該例外的一個(gè)對(duì)象,并把它交給運(yùn)行時(shí)系統(tǒng),運(yùn)行時(shí)系統(tǒng)尋找相應(yīng)的代碼來處理這一例外。我們把生成例外對(duì)象并把它提交給運(yùn)行時(shí)系統(tǒng)的過程稱為拋棄(throw)一個(gè)例外。運(yùn)行時(shí)系統(tǒng)在方法的調(diào)用棧中查找,從生成例外的方法開始進(jìn)行回朔,直到找到包含相應(yīng)例外處理的方法為止,這一個(gè)過程稱為捕獲(catch)一個(gè)例外。
          2.2 Throwable類及其子類
           用面向?qū)ο蟮姆椒ㄌ幚砝猓捅仨毥㈩惖膶哟巍n?Throwable位于這一類層次的最頂層,只有它的后代才可以做為一個(gè)例外被拋棄。圖1表示了例外處理的類層次。
          從圖中可以看出,類Throwable有兩個(gè)直接子類:Error和Exception。Error類對(duì)象(如動(dòng)態(tài)連接錯(cuò)誤等),由Java虛擬機(jī)生成并拋棄(通常,Java程序不對(duì)這類例外進(jìn)行處理);Exception類對(duì)象是Java程序處理或拋棄的對(duì)象。它有各種不同的子類分別對(duì)應(yīng)于不同類型的例外。其中類RuntimeException代表運(yùn)行時(shí)由Java虛擬機(jī)生成的例外,如算術(shù)運(yùn)算例外ArithmeticException(由除0錯(cuò)等導(dǎo)致)、數(shù)組越界例外ArrayIndexOutOfBoundsException等;其它則為非運(yùn)行時(shí)例外,如輸入輸出例外IOException等。Java編譯器要求Java程序必須捕獲或聲明所有的非運(yùn)行時(shí)例外,但對(duì)運(yùn)行時(shí)例外可以不做處理。
           

          2.3  異常處理關(guān)鍵字
          Java的異常處理是通過5個(gè)關(guān)鍵字來實(shí)現(xiàn)的:try,catch,throw,throws,finally。JB的在線幫助中對(duì)這幾個(gè)關(guān)鍵字是這樣解釋的:
          Throws:  Lists the exceptions a method could throw.
          Throw:   Transfers control of the method to the exception handler.
          Try:    Opening exception-handling statement.
          Catch:  Captures the exception.
          Finally: Runs its code before terminating the program.
          2.3.1 try語句 
          try語句用大括號(hào){}指定了一段代碼,該段代碼可能會(huì)拋棄一個(gè)或多個(gè)例外。
          2.3.2 catch語句 
          catch語句的參數(shù)類似于方法的聲明,包括一個(gè)例外類型和一個(gè)例外對(duì)象。例外類型必須為Throwable類的子類,它指明了catch語句所處理的例外類型,例外對(duì)象則由運(yùn)行時(shí)系統(tǒng)在try所指定的代碼塊中生成并被捕獲,大括號(hào)中包含對(duì)象的處理,其中可以調(diào)用對(duì)象的方法。
          catch語句可以有多個(gè),分別處理不同類的例外。Java運(yùn)行時(shí)系統(tǒng)從上到下分別對(duì)每個(gè)catch語句處理的例外類型進(jìn)行檢測,直到找到類型相匹配的catch語句為止。這里,類型匹配指catch所處理的例外類型與生成的例外對(duì)象的類型完全一致或者是它的父類,因此,catch語句的排列順序應(yīng)該是從特殊到一般。
          也可以用一個(gè)catch語句處理多個(gè)例外類型,這時(shí)它的例外類型參數(shù)應(yīng)該是這多個(gè)例外類型的父類,程序設(shè)計(jì)中要根據(jù)具體的情況來選擇catch語句的例外處理類型。 
          2.3.3 finally語句 
          try所限定的代碼中,當(dāng)拋棄一個(gè)例外時(shí),其后的代碼不會(huì)被執(zhí)行。通過finally語句可以指定一塊代碼。無論try所指定的程序塊中拋棄或不拋棄例外,也無論catch語句的例外類型是否與所拋棄的例外的類型一致,finally所指定的代碼都要被執(zhí)行,它提供了統(tǒng)一的出口。通常在finally語句中可以進(jìn)行資源的清除工作。如關(guān)閉打開的文件等。
          2.3.4 throws語句 
          throws總是出現(xiàn)在一個(gè)函數(shù)頭中,用來標(biāo)明該成員函數(shù)可能拋出的各種異常。對(duì)大多數(shù)Exception子類來說,Java 編譯器會(huì)強(qiáng)迫你聲明在一個(gè)成員函數(shù)中拋出的異常的類型。如果異常的類型是Error或 RuntimeException, 或它們的子類,這個(gè)規(guī)則不起作用, 因?yàn)檫@在程序的正常部分中是不期待出現(xiàn)的。 如果你想明確地拋出一個(gè)RuntimeException,你必須用throws語句來聲明它的類型。
          2.3.5 throw語句 
          throw總是出現(xiàn)在函數(shù)體中,用來拋出一個(gè)異常。程序會(huì)在throw語句后立即終止,它后面的語句執(zhí)行不到,然后在包含它的所有try塊中(可能在上層調(diào)用函數(shù)中)從里向外尋找含有與其匹配的catch子句的try塊。

          3 關(guān)鍵字及其中語句流程詳解

          3.1 try的嵌套
          你可以在一個(gè)成員函數(shù)調(diào)用的外面寫一個(gè)try語句,在這個(gè)成員函數(shù)內(nèi)部,寫另一個(gè)try語句保護(hù)其他代碼。每當(dāng)遇到一個(gè)try語句,異常的框架就放到堆棧上面,直到所有的try語句都完成。如果下一級(jí)的try語句沒有對(duì)某種異常進(jìn)行處理,堆棧就會(huì)展開,直到遇到有處理這種異常的try語句。下面是一個(gè)try語句嵌套的例子。
          class MultiNest {
              static void procedure() {
                  try {
                      int a = 0;
                      int b = 42/a;
                  } catch(java.lang.ArithmeticException e) {
                      System.out.println("in procedure, catch ArithmeticException: " + e);
                  }
              }
              public static void main(String args[]) {
                  try {
                      procedure();
                  } catch(java.lang. Exception e) {
                      System.out.println("in main, catch Exception: " + e);
                  }
              }
          }
          這個(gè)例子執(zhí)行的結(jié)果為:
          in procedure, catch ArithmeticException: java.lang.ArithmeticException: / by zero
          成員函數(shù)procedure里有自己的try/catch控制,所以main不用去處理 ArrayIndexOutOfBoundsException;當(dāng)然如果如同最開始我們做測試的例子一樣,在procedure中catch到異常時(shí)使用throw e;語句將異常拋出,那么main當(dāng)然還是能夠捕捉并處理這個(gè)procedure拋出來的異常。例如在procedure函數(shù)的catch中的System.out語句后面增加throw e;語句之后,執(zhí)行結(jié)果就變?yōu)椋?br /> in procedure, catch ArithmeticException: java.lang.ArithmeticException: / by zero
          in main, catch Exception: java.lang.ArithmeticException: / by zero

          3.2 try-catch程序塊的執(zhí)行流程以及執(zhí)行結(jié)果
          相對(duì)于try-catch-finally程序塊而言,try-catch的執(zhí)行流程以及執(zhí)行結(jié)果還是比較簡單的。
          首先執(zhí)行的是try語句塊中的語句,這時(shí)可能會(huì)有以下三種情況:
              1.如果try塊中所有語句正常執(zhí)行完畢,那么就不會(huì)有其他的“動(dòng)做”被執(zhí)行,整個(gè)try-catch程序塊正常完成。
              2.如果try語句塊在執(zhí)行過程中碰到異常V,這時(shí)又分為兩種情況進(jìn)行處理:
          -->如果異常V能夠被與try相應(yīng)的catch塊catch到,那么第一個(gè)catch到這個(gè)異常的catch塊(也是離try最近的一個(gè)與異常V匹配的catch塊)將被執(zhí)行;如果catch塊執(zhí)行正常,那么try-catch程序塊的結(jié)果就是“正常完成”;如果該catch塊由于原因R突然中止,那么try-catch程序塊的結(jié)果就是“由于原因R突然中止(completes abruptly)”。
          -->如果異常V沒有catch塊與之匹配,那么這個(gè)try-catch程序塊的結(jié)果就是“由于拋出異常V而突然中止(completes abruptly)”。
              3. 如果try由于其他原因R突然中止(completes abruptly),那么這個(gè)try-catch程序塊的結(jié)果就是“由于原因R突然中止(completes abruptly)”。

          3.3 try-catch-finally程序塊的執(zhí)行流程以及執(zhí)行結(jié)果
          try-catch-finally程序塊的執(zhí)行流程以及執(zhí)行結(jié)果比較復(fù)雜。
          首先執(zhí)行的是try語句塊中的語句,這時(shí)可能會(huì)有以下三種情況:
          1.如果try塊中所有語句正常執(zhí)行完畢,那么finally塊的居于就會(huì)被執(zhí)行,這時(shí)分為以下兩種情況:
          -->如果finally塊執(zhí)行順利,那么整個(gè)try-catch-finally程序塊正常完成。
          -->如果finally塊由于原因R突然中止,那么try-catch-finally程序塊的結(jié)局是“由于原因R突然中止(completes abruptly)”
          2.如果try語句塊在執(zhí)行過程中碰到異常V,這時(shí)又分為兩種情況進(jìn)行處理:
          -->如果異常V能夠被與try相應(yīng)的catch塊catch到,那么第一個(gè)catch到這個(gè)異常的catch塊(也是離try最近的一個(gè)與異常V匹配的catch塊)將被執(zhí)行;這時(shí)就會(huì)有兩種執(zhí)行結(jié)果:
          -->如果catch塊執(zhí)行正常,那么finally塊將會(huì)被執(zhí)行,這時(shí)分為兩種情況:
          -->如果finally塊執(zhí)行順利,那么整個(gè)try-catch-finally程序塊正常完成。
          -->如果finally塊由于原因R突然中止,那么try-catch-finally程序塊的結(jié)局是“由于原因R突然中止(completes abruptly)”
          -->如果catch塊由于原因R突然中止,那么finally模塊將被執(zhí)行,分為兩種情況:
          -->如果如果finally塊執(zhí)行順利,那么整個(gè)try-catch-finally程序塊的結(jié)局是“由于原因R突然中止(completes abruptly)”。
          -->如果finally塊由于原因S突然中止,那么整個(gè)try-catch-finally程序塊的結(jié)局是“由于原因S突然中止(completes abruptly)”,原因R將被拋棄。
          (注意,這里就正好和我們的例子相符合,雖然我們?cè)趖estEx2中使用throw e拋出了異常,但是由于testEx2中有finally塊,而finally塊的執(zhí)行結(jié)果是complete abruptly的(別小看這個(gè)用得最多的return,它也是一種導(dǎo)致complete abruptly的原因之一啊——后文中有關(guān)于導(dǎo)致complete abruptly的原因分析),所以整個(gè)try-catch-finally程序塊的結(jié)果是“complete abruptly”,所以在testEx1中調(diào)用testEx2時(shí)是捕捉不到testEx1中拋出的那個(gè)異常的,而只能將finally中的return結(jié)果獲取到。
          如果在你的代碼中期望通過捕捉被調(diào)用的下級(jí)函數(shù)的異常來給定返回值,那么一定要注意你所調(diào)用的下級(jí)函數(shù)中的finally語句,它有可能會(huì)使你throw出來的異常并不能真正被上級(jí)調(diào)用函數(shù)可見的。當(dāng)然這種情況是可以避免的,以testEx2為例:如果你一定要使用finally而且又要將catch中throw的e在testEx1中被捕獲到,那么你去掉testEx2中的finally中的return就可以了。
          這個(gè)事情已經(jīng)在OMC2.0的MIB中出現(xiàn)過啦:服務(wù)器的異常不能完全被反饋到客戶端。)
          -->如果異常V沒有catch塊與之匹配,那么finally模塊將被執(zhí)行,分為兩種情況:
          -->如果finally塊執(zhí)行順利,那么整個(gè)try-catch-finally程序塊的結(jié)局就是“由于拋出異常V而突然中止(completes abruptly)”。
          -->如果finally塊由于原因S突然中止,那么整個(gè)try-catch-finally程序塊的結(jié)局是“由于原因S突然中止(completes abruptly)”,異常V將被拋棄。
          3.如果try由于其他原因R突然中止(completes abruptly),那么finally塊被執(zhí)行,分為兩種情況:
          -->如果finally塊執(zhí)行順利,那么整個(gè)try-catch-finally程序塊的結(jié)局是“由于原因R突然中止(completes abruptly)”。
          -->如果finally塊由于原因S突然中止,那么整個(gè)try-catch-finally程序塊的結(jié)局是“由于原因S突然中止(completes abruptly)”,原因R將被拋棄。
          3.4 try-catch-finally程序塊中的return
          從上面的try-catch-finally程序塊的執(zhí)行流程以及執(zhí)行結(jié)果一節(jié)中可以看出無論try或catch中發(fā)生了什么情況,finally都是會(huì)被執(zhí)行的,那么寫在try或者catch中的return語句也就不會(huì)真正的從該函數(shù)中跳出了,它的作用在這種情況下就變成了將控制權(quán)(語句流程)轉(zhuǎn)到finally塊中;這種情況下一定要注意返回值的處理。
          例如,在try或者catch中return false了,而在finally中又return true,那么這種情況下不要期待你的try或者catch中的return false的返回值false被上級(jí)調(diào)用函數(shù)獲取到,上級(jí)調(diào)用函數(shù)能夠獲取到的只是finally中的返回值,因?yàn)閠ry或者catch中的return語句只是轉(zhuǎn)移控制權(quán)的作用。
          3.5 如何拋出異常
          如果你知道你寫的某個(gè)函數(shù)有可能拋出異常,而你又不想在這個(gè)函數(shù)中對(duì)異常進(jìn)行處理,只是想把它拋出去讓調(diào)用這個(gè)函數(shù)的上級(jí)調(diào)用函數(shù)進(jìn)行處理,那么有兩種方式可供選擇:
          第一種方式:直接在函數(shù)頭中throws SomeException,函數(shù)體中不需要try/catch。比如將最開始的例子中的testEx2改為下面的方式,那么testEx1就能捕捉到testEx2拋出的異常了。
              boolean testEx2() throws Exception{
                  boolean ret = true;
                  int b=12;
                  int c;
                  for (int i=2;i>=-2;i--){
                      c=b/i;
                      System.out.println("i="+i);
                  }
                  return true;   
          }
          第二種方式:使用try/catch,在catch中進(jìn)行一定的處理之后(如果有必要的話)拋出某種異常。例如上面的testEx2改為下面的方式,testEx1也能捕獲到它拋出的異常:
              boolean testEx2() throws Exception{
                  boolean ret = true;
                  try{
                      int b=12;
                      int c;
                      for (int i=2;i>=-2;i--){
                          c=b/i;
                          System.out.println("i="+i);
                      }
                      return true;
                  }catch (Exception e){
                      System.out.println("testEx2, catch exception");
                      Throw e;
                  }
              }
          第三種方法:使用try/catch/finally,在catch中進(jìn)行一定的處理之后(如果有必要的話)拋出某種異常。例如上面的testEx2改為下面的方式,testEx1也能捕獲到它拋出的異常:
              boolean testEx2() throws Exception{
                  boolean ret = true;
                  try{
                      int b=12;
                      int c;
                      for (int i=2;i>=-2;i--){
                          c=b/i;
                          System.out.println("i="+i);
                          throw new Exception("aaa");
                      }
                      return true;
                  }catch (java.lang.ArithmeticException e){
                      System.out.println("testEx2, catch exception");
                      ret = false;
                      throw new Exception("aaa");
                  }finally{
                      System.out.println("testEx2, finally; return value="+ret);
                  }
              }
          4  關(guān)于abrupt completion
          前面提到了complete abruptly(暫且理解為“突然中止”或者“異常結(jié)束”吧),它主要包含了兩種大的情形:abrupt completion of expressions and statements,下面就分兩種情況進(jìn)行解釋。
          4.1 Normal and Abrupt Completion of Evaluation
          每一個(gè)表達(dá)式(expression)都有一種使得其包含的計(jì)算得以一步步進(jìn)行的正常模式,如果每一步計(jì)算都被執(zhí)行且沒有異常拋出,那么就稱這個(gè)表達(dá)式“正常結(jié)束(complete normally)”;如果這個(gè)表達(dá)式的計(jì)算拋出了異常,就稱為“異常結(jié)束(complete abruptly)”。異常結(jié)束通常有一個(gè)相關(guān)聯(lián)的原因(associated reason),通常也就是拋出一個(gè)異常V。
          與表達(dá)式、操作符相關(guān)的運(yùn)行期異常有:
          -->A class instance creation expression, array creation expression , or string concatenation operatior expression throws an OutOfMemoryError if there is insufficient memory available.
          -->An array creation expression throws a NegativeArraySizeException if the value of any dimension expression is less than zero.
          -->A field access throws a NullPointerException if the value of the object reference  expression is null.
          -->A method invocation expression that invokes an instance method throws a NullPointerException if the target reference is null.
          -->An array access throws a NullPointerException if the value of the array reference  expression is null.
          -->An array access throws an ArrayIndexOutOfBoundsException if the value of the array index expression is negative or greater than or equal to the length of the array.
          -->A cast throws a ClassCastException if a cast is found to be impermissible at run time.
          -->An integer division or integer remainder operator throws an ArithmeticException if the value of the right-hand operand expression is zero.
          -->An assignment to an array component of reference type throws an ArrayStoreException when the value to be assigned is not compatible with the component type of the array.
          4.2 Normal and Abrupt Completion of Statements
          正常情況我們就不多說了,在這里主要是列出了abrupt completion的幾種情況:
          -->break, continue, and return 語句將導(dǎo)致控制權(quán)的轉(zhuǎn)換,從而使得statements不能正常地、完整地執(zhí)行。
          -->某些表達(dá)式的計(jì)算也可能從java虛擬機(jī)拋出異常,這些表達(dá)式在上一小節(jié)中已經(jīng)總結(jié)過了;一個(gè)顯式的的throw語句也將導(dǎo)致異常的拋出。拋出異常也是導(dǎo)致控制權(quán)的轉(zhuǎn)換的原因(或者說是阻止statement正常結(jié)束的原因)。
          如果上述事件發(fā)生了,那么這些statement就有可能使得其正常情況下應(yīng)該都執(zhí)行的語句不能完全被執(zhí)行到,那么這些statement也就是被稱為是complete abruptly.
          導(dǎo)致abrupt completion的幾種原因:
          -->A break with no label
          -->A break with a given label
          -->A continue with no label
          -->A continue with a given label
          -->A return with no value
          -->A return with a given value A
          -->throw with a given value, including exceptions thrown by the Java virtual machine
          5 關(guān)于我們的編程的一點(diǎn)建議
          弄清楚try-catch-finally的執(zhí)行情況后我們才能正確使用它。
          如果我們使用的是try-catch-finally語句塊,而我們又需要保證有異常時(shí)能夠拋出異常,那么在finally語句中就不要使用return語句了(finally語句塊的最重要的作用應(yīng)該是釋放申請(qǐng)的資源),因?yàn)?span id="wmqeeuq" class="hilite1">finally中的return語句會(huì)導(dǎo)致我們的throw e被拋棄,在這個(gè)try-catch-finally的外面將只能看到finally中的返回值(除非在finally中拋出異常)。(我們需要記住:不僅throw語句是abrupt completion 的原因,return、break、continue等這些看起來很正常的語句也是導(dǎo)致abrupt completion的原因。)

          posted on 2009-05-08 15:23 lanxin1020 閱讀(213) 評(píng)論(0)  編輯  收藏 所屬分類: j2se
          主站蜘蛛池模板: 朝阳市| 玛纳斯县| 城口县| 黄龙县| 珲春市| 潼关县| 吴桥县| 泸溪县| 唐山市| 禹州市| 东乡县| 静安区| 杂多县| 广河县| 永兴县| 固阳县| 夹江县| 天门市| 佛教| 泽库县| 石河子市| 天峨县| 基隆市| 吴桥县| 建平县| 昌都县| 定结县| 台南市| 宕昌县| 牟定县| 定日县| 勐海县| 邯郸市| 门头沟区| 西林县| 吉安市| 达州市| 镇江市| 祁门县| 永清县| 分宜县|