自由飛翔

          我在仰望,java之上

          統計

          留言簿(2)

          我關注的blog

          閱讀排行榜

          評論排行榜

          轉載:JAVA異常機制介紹/如何正確的進行JAVA異常處理

           文章來源:
           作者:Maverick
           blog:http://blog.csdn.net/zhaohuabing  

          1  引言
          在JAVA語言出現以前,傳統的異常處理方式多采用返回值來標識程序出現的異常情況,這種方式雖然為程序員所熟悉,但卻有多個壞處。首先,一個API可以返回任意的返回值,而這些返回值本身并不能解釋該返回值是否代表一個異常情況發生了和該異常的具體情況,需要調用API的程序自己判斷并解釋返回值的含義。其次,并沒有一種機制來保證異常情況一定會得到處理,調用程序可以簡單的忽略該返回值,需要調用API的程序員記住去檢測返回值并處理異常情況。這種方式還讓程序代碼變得晦澀冗長,當進行IO操作等容易出現異常情況的處理時,你會發現代碼的很大部分用于處理異常情況的switch分支,程序代碼的可讀性變得很差。
          上面提到的問題,JAVA的異常處理機制提供了很好的解決方案。通過拋出JDK預定義或者自定義的異常,能夠表明程序中出現了什么樣的異常情況;而且JAVA的語言機制保證了異常一定會得到恰當的處理;合理的使用異常處理機制,會讓程序代碼清晰易懂。
          2 JAVA異常的處理機制
              當程序中拋出一個異常后,程序從程序中導致異常的代碼處跳出,java虛擬機檢測尋找和try關鍵字匹配的處理該異常的catch塊,如果找到,將控制權交到catch塊中的代碼,然后繼續往下執行程序,try塊中發生異常的代碼不會被重新執行。如果沒有找到處理該異常的catch塊,在所有的finally塊代碼被執行和當前線程的所屬的ThreadGroup的uncaughtException方法被調用后,遇到異常的當前線程被中止。
          3 JAVA異常的類層次
          JAVA異常的類層次如下圖所示:

          圖1 JAVA異常的類層次
          Throwable是所有異常的基類,程序中一般不會直接拋出Throwable對象,Exception和Error是Throwable的子類,Exception下面又有RuntimeException和一般的Exception兩類。可以把JAVA異常分為三類:
                  第一類是Error,Error表示程序在運行期間出現了十分嚴重、不可恢復的錯誤,在這種情況下應用程序只能中止運行,例如JAVA 虛擬機出現錯誤。Error是一種unchecked Exception,編譯器不會檢查Error是否被處理,在程序中不用捕獲Error類型的異常;一般情況下,在程序中也不應該拋出Error類型的異常。
                  第二類是RuntimeException, RuntimeException 是一種unchecked Exception,即表示編譯器不會檢查程序是否對RuntimeException作了處理,在程序中不必捕獲RuntimException類型的異常,也不必在方法體聲明拋出RuntimeException類。RuntimeException發生的時候,表示程序中出現了編程錯誤,所以應該找出錯誤修改程序,而不是去捕獲RuntimeException。
                  第三類是一般的checked Exception,這也是在編程中使用最多的Exception,所有繼承自Exception并且不是RuntimeException的異常都是checked Exception,如圖1中的IOException和ClassNotFoundException。JAVA 語言規定必須對checked Exception作處理,編譯器會對此作檢查,要么在方法體中聲明拋出checked Exception,要么使用catch語句捕獲checked Exception進行處理,不然不能通過編譯。checked Exception用于以下的語義環境:

          (1) 該異常發生后是可以被恢復的,如一個Internet連接發生異常被中止后,可以重新連接再進行后續操作。
          (2) 程序依賴于不可靠的外部條件,該依賴條件可能出錯,如系統IO。
          (3) 該異常發生后并不會導致程序處理錯誤,進行一些處理后可以繼續后續操作。

          4 JAVA異常處理中的注意事項
          合理使用JAVA異常機制可以使程序健壯而清晰,但不幸的是,JAVA異常處理機制常常被錯誤的使用,下面就是一些關于Exception的注意事項:

          1. 不要忽略checked Exception
          請看下面的代碼:
          try
          {
            method1();  //method1拋出ExceptionA
          }
          catch(ExceptionA e)
          {
              e.printStackTrace();
          }
          上面的代碼似乎沒有什么問題,捕獲異常后將異常打印,然后繼續執行。事實上在catch塊中對發生的異常情況并沒有作任何處理(打印異常不能是算是處理異常,因為在程序交付運行后調試信息就沒有什么用處了)。這樣程序雖然能夠繼續執行,但是由于這里的操作已經發生異常,將會導致以后的操作并不能按照預期的情況發展下去,可能導致兩個結果:
          一是由于這里的異常導致在程序中別的地方拋出一個異常,這種情況會使程序員在調試時感到迷惑,因為新的異常拋出的地方并不是程序真正發生問題的地方,也不是發生問題的真正原因;
          另外一個是程序繼續運行,并得出一個錯誤的輸出結果,這種問題更加難以捕捉,因為很可能把它當成一個正確的輸出。
          那么應該如何處理呢,這里有四個選擇:

          (1) 處理異常,進行修復以讓程序繼續執行。
          (2) 重新拋出異常,在對異常進行分析后發現這里不能處理它,那么重新拋出異常,讓調用者處理。
          (3) 將異常轉換為用戶可以理解的自定義異常再拋出,這時應該注意不要丟失原始異常信息(見5)。
          (4) 不要捕獲異常。

          因此,當捕獲一個unchecked Exception的時候,必須對異常進行處理;如果認為不必要在這里作處理,就不要捕獲該異常,在方法體中聲明方法拋出異常,由上層調用者來處理該異常。

          2. 不要一次捕獲所有的異常
          請看下面的代碼:
          try
          {
            method1();  //method1拋出ExceptionA
              method2();  //method1拋出ExceptionB
              method3();  //method1拋出ExceptionC
          }
          catch(Exception e)
          {
              ……
          }
          這是一個很誘人的方案,代碼中使用一個catch子句捕獲了所有異常,看上去完美而且簡潔,事實上很多代碼也是這樣寫的。但這里有兩個潛在的缺陷,一是針對try塊中拋出的每種Exception,很可能需要不同的處理和恢復措施,而由于這里只有一個catch塊,分別處理就不能實現。二是try塊中還可能拋出RuntimeException,代碼中捕獲了所有可能拋出的RuntimeException而沒有作任何處理,掩蓋了編程的錯誤,會導致程序難以調試。
          下面是改正后的正確代碼:
          try
          {
            method1();  //method1拋出ExceptionA
              method2();  //method1拋出ExceptionB
              method3();  //method1拋出ExceptionC
          }
          catch(ExceptionA e)
          {
              ……
          }
          catch(ExceptionB e)
          {
              ……
          }
          catch(ExceptionC e)
          {
              ……
          }


          3. 使用finally塊釋放資源
              finally關鍵字保證無論程序使用任何方式離開try塊,finally中的語句都會被執行。在以下三種情況下會進入finally塊:
          (1) try塊中的代碼正常執行完畢。
          (2) 在try塊中拋出異常。
          (3) 在try塊中執行return、break、continue。
          因此,當你需要一個地方來執行在任何情況下都必須執行的代碼時,就可以將這些
          代碼放入finally塊中。當你的程序中使用了外界資源,如數據庫連接,文件等,必須將釋放這些資源的代碼寫入finally塊中。
          必須注意的是,在finally塊中不能拋出異常。JAVA異常處理機制保證無論在任何情況下必須先執行finally塊然后在離開try塊,因此在try塊中發生異常的時候,JAVA虛擬機先轉到finally塊執行finally塊中的代碼,finally塊執行完畢后,再向外拋出異常。如果在finally塊中拋出異常,try塊捕捉的異常就不能拋出,外部捕捉到的異常就是finally塊中的異常信息,而try塊中發生的真正的異常堆棧信息則丟失了。
          請看下面的代碼:

          Connection  con = null;
          try
          {
              con = dataSource.getConnection();
              ……
          }
          catch(SQLException e)
          {
              ……
              throw e;//進行一些處理后再將數據庫異常拋出給調用者處理
          }
          finally
          {
              try
              {
                  con.close();
              }
              catch(SQLException e)
          {
              e.printStackTrace();
              ……
          }
          }
          運行程序后,調用者得到的信息如下
          java.lang.NullPointerException
           at myPackage.MyClass.method1(methodl.java:266)
          而不是我們期望得到的數據庫異常。這是因為這里的con是null的關系,在finally語句中拋出了NullPointerException,在finally塊中增加對con是否為null的判斷可以避免產生這種情況。

          4. 異常不能影響對象的狀態
          異常產生后不能影響對象的狀態,這是異常處理中的一條重要規則。 在一個函數
          中發生異常后,對象的狀態應該和調用這個函數之前保持一致,以確保對象處于正確的狀態中。
          如果對象是不可變對象(不可變對象指調用構造函數創建后就不能改變的對象,即
              創建后沒有任何方法可以改變對象的狀態),那么異常發生后對象狀態肯定不會改變。如果是可變對象,必須在編程中注意保證異常不會影響對象狀態。有三個方法可以達到這個目的:
          (1) 將可能產生異常的代碼和改變對象狀態的代碼分開,先執行可能產生異常的代碼,如果產生異常,就不執行改變對象狀態的代碼。
          (2) 對不容易分離產生異常代碼和改變對象狀態代碼的方法,定義一個recover方法,在異常產生后調用recover方法修復被改變的類變量,恢復方法調用前的類狀態。
          (3) 在方法中使用對象的拷貝,這樣當異常發生后,被影響的只是拷貝,對象本身不會受到影響。

          5. 丟失的異常
          請看下面的代碼:
          public void method2()
          {
          try
          {
              ……
              method1();  //method1進行了數據庫操作
          }
          catch(SQLException e)
          {
              ……
              throw new MyException(“發生了數據庫異常:”+e.getMessage);
          }
          }
          public void method3()
          {
              try
          {
              method2();
          }
          catch(MyException e)
          {
              e.printStackTrace();
              ……
          }
          }
          上面method2的代碼中,try塊捕獲method1拋出的數據庫異常SQLException后,拋出了新的自定義異常MyException。這段代碼是否并沒有什么問題,但看一下控制臺的輸出:
          MyException:發生了數據庫異常:對象名稱 'MyTable' 無效。
          at MyClass.method2(MyClass.java:232)
          at MyClass.method3(MyClass.java:255)
          原始異常SQLException的信息丟失了,這里只能看到method2里面定義的MyException的堆棧情況;而method1中發生的數據庫異常的堆棧則看不到,如何排錯呢,只有在method1的代碼行中一行行去尋找數據庫操作語句了,祈禱method1的方法體短一些吧。
          JDK的開發者們也意識到了這個情況,在JDK1.4.1中,Throwable類增加了兩個構造方法,public Throwable(Throwable cause)和public Throwable(String message,Throwable cause),在構造函數中傳入的原始異常堆棧信息將會在printStackTrace方法中打印出來。但對于還在使用JDK1.3的程序員,就只能自己實現打印原始異常堆棧信息的功能了。實現過程也很簡單,只需要在自定義的異常類中增加一個原始異常字段,在構造函數中傳入原始異常,然后重載printStackTrace方法,首先調用類中保存的原始異常的printStackTrace方法,然后再調用super.printStackTrace方法就可以打印出原始異常信息了。可以這樣定義前面代碼中出現的MyException類:
          public class MyExceptionextends Exception
          {
              //構造函數
              public SMException(Throwable cause)
              {
                  this.cause_ = cause;
              }

              public MyException(String s,Throwable cause)
              {
                  super(s);
                  this.cause_ = cause;
              }
              //重載printStackTrace方法,打印出原始異常堆棧信息
              public void printStackTrace()
              {
                  if (cause_ != null)
                  {
                      cause_.printStackTrace();
                  }
                  super.printStackTrace(s);
              }

              public void printStackTrace(PrintStream s)
              {
                  if (cause_ != null)
                  {
                      cause_.printStackTrace(s);
                  }
                  super.printStackTrace(s);
              }

              public void printStackTrace(PrintWriter s)
              {
                  if (cause_ != null)
                  {
                      cause_.printStackTrace(s);
                  }
                  super.printStackTrace(s);
              }
               //原始異常
               private Throwable cause_;
          }

          6. 不要使用同時使用異常機制和返回值來進行異常處理
          下面是我們項目中的一段代碼
          try
          {
              doSomething(); 
          }
          catch(MyException e)
          {
          if(e.getErrcode == -1)
          {
              ……
          }
          if(e.getErrcode == -2)
          {
             ……
          }
          ……
          }
          假如在過一段時間后來看這段代碼,你能弄明白是什么意思嗎?混合使用JAVA異常處理機制和返回值使程序的異常處理部分變得“丑陋不堪”,并難以理解。如果有多種不同的異常情況,就定義多種不同的異常,而不要像上面代碼那樣綜合使用Exception和返回值。
          修改后的正確代碼如下:
          try
          {
              doSomething();  //拋出MyExceptionA和MyExceptionB
          }
          catch(MyExceptionA e)
          {
          ……
          }
          catch(MyExceptionB e)
          {
              ……
          }


          7. 不要讓try塊過于龐大
          出于省事的目的,很多人習慣于用一個龐大的try塊包含所有可能產生異常的代碼,
          這樣有兩個壞處:
          閱讀代碼的時候,在try塊冗長的代碼中,不容易知道到底是哪些代碼會拋出哪些異常,不利于代碼維護。
          使用try捕獲異常是以程序執行效率為代價的,將不需要捕獲異常的代碼包含在try塊中,影響了代碼執行的效率。



          Gavin

          posted on 2011-09-30 01:08 GavinMiao 閱讀(358) 評論(0)  編輯  收藏 所屬分類: corejava

          主站蜘蛛池模板: 乐东| 内丘县| 武威市| 积石山| 静海县| 丰镇市| 江油市| 马关县| 清原| 威远县| 鹤山市| 田东县| 永顺县| 中牟县| 登封市| 资源县| 资兴市| 七台河市| 平乡县| 沾益县| 竹山县| 凉城县| 钟山县| 图片| 荥经县| 汾阳市| 岳阳县| 安国市| 阳信县| 鄂温| 邹城市| 嘉峪关市| 黄山市| 绵竹市| 永宁县| 八宿县| 阿勒泰市| 拜城县| 古浪县| 织金县| 潼南县|