1. 程式人生 > >Java異常:選擇Checked Exception還是Unchecked Exception?

Java異常:選擇Checked Exception還是Unchecked Exception?

原文地址:http://blog.csdn.net/kingzone_2008/article/details/8535287

Java包含兩種異常:checked異常unchecked異常。C#只有unchecked異常。checked和unchecked異常之間的區別是:

  1. Checked異常必須被顯式地捕獲或者傳遞,如Basic try-catch-finally Exception Handling一文中所說。而unchecked異常則可以不必捕獲或丟擲。
  2. Checked異常繼承java.lang.Exception類。Unchecked異常繼承自java.lang.RuntimeException類。

有許多支援或者反對二者甚至是否應該使用checked異常的爭論。本文將討論一些常見的觀點。開始之前,先澄清一個問題:

Checked和unchecked異常從功能的角度來講是等價的。可以用checked異常實現的功能必然也可以用unchecked異常實現,反之亦然。

選擇checked異常還是unchecked異常是個人習慣或者組織規定問題。並不存在誰比誰強大的問題。

一個簡單的例子

在討論checked和unchecked異常的優缺點前先看一下程式碼中如下使用它們。下面是一個丟擲checked異常的方法,另一個方法呼叫了它:

  1. publicvoid storeDataFromUrl(String url){  
  2.     try {  
  3.         String data = readDataFromUrl(url);  
  4.     } catch (BadUrlException e) {  
  5.         e.printStackTrace();  
  6.     }  
  7. }  
  8. public String readDataFromUrl(String url)  
  9. throws BadUrlException{  
  10.     if(isUrlBad(url)){  
  11.         thrownew BadUrlException("Bad URL: " + url);  
  12.     }  
  13.     String data = null
    ;  
  14.     //read lots of data over HTTP and return
  15.     //it as a String instance.
  16.     return data;  
  17. }  
readDataFromUrl()方法丟擲了BadUrlException。BadUrlException是我自己實現的一個類。由於BadUrlException繼承自java.lang.Exception,因而它是checked異常:
  1. publicclass BadUrlException extends Exception {  
  2.     public BadUrlException(String s) {  
  3.         super(s);  
  4.     }  
  5. }  
如果storeDataFromUrl()方法想要呼叫readDataFromUrl(),它只有兩種選擇。要麼捕獲BadUrlException,要麼沿著呼叫棧繼續向上傳播該異常。上面的程式碼中storeDataFromUrl() 捕獲了異常。向上傳播異常的實現方法如下:
  1. publicvoid storeDataFromUrl(String url)  
  2. throws BadUrlException{  
  3.     String data = readDataFromUrl(url);  
  4. }  
可以看到,上述程式碼去掉了catch塊,方法宣告中加上了throws BadUrlException。下面,討論一下unchecked異常的實現方法。首先,將BadUrlException改為繼承自java.lang.RuntimeException:
  1. publicclass BadUrlException extends RuntimeException {  
  2.     public BadUrlException(String s) {  
  3.         super(s);  
  4.     }  
  5. }  
然後,把方法中的異常改為unchecked BadUrlException:
  1. publicvoid storeDataFromUrl(String url){  
  2.     String data = readDataFromUrl(url);  
  3. }  
  4. public String readDataFromUrl(String url) {  
  5.     if(isUrlBad(url)){  
  6.         thrownew BadUrlException("Bad URL: " + url);  
  7.     }  
  8.     String data = null;  
  9.     //read lots of data over HTTP and
  10.     //return it as a String instance.
  11.     return data;  
  12. }  
注意,readDataFromUrl()方法不再宣告丟擲BadUrlException。storeDataFromUrl()方法也不必捕獲BadUrlException。storeDataFromUrl()也可以捕獲異常,但不再是必須的了,而且它也不必宣告傳播異常。

Checked 還是Unchecked?

上一節我們已經討論了checked異常和unchecked異常程式碼實現上的區別,下面深入分析二者的適用情況(支援和反對二者的觀點)。

一些Java書籍(如Suns Java Tutorial)中建議在遇到可恢復的錯誤時採用checked異常,遇到不可恢復的異常時採用unchecked異常。事實上,大多數應用必須從幾乎所有異常(包括NullPointerException,IllegalArgumentException和許多其他unchecked異常)中恢復。執行失敗的action/transaction會被取消,但是應用程式必須能繼續處理後續的action或transaction。關閉一個應用的唯一合法時機是應用程式啟動時。例如,如果配置檔案丟失而且應用程式依賴於它,那麼這時關閉應用程式是合法的。

我建議的使用策略是:選擇checked異常或unchecked異常中的一種使用。混合使用經常導致混亂和不一致。如果你是一個經驗豐富的程式設計師,那麼根據自己的需要使用吧。

下面是支援和反對checked/unchecked異常的一些最常見的觀點。支援一種型別的exception的觀點通常意味著反對另一種(支援checked = 反對unchecked,支援unchecked = 反對checked)。因此,只列出了支援checked異常或unchecked異常的列表。

  1. 支援Checked異常:
    編譯器強制檢查,checked異常必須被捕獲或者傳播,這樣就不會忘記處理異常。
  2. 支援Checked異常:
    Unchecked異常容易忘記處理,由於編譯器不強制程式設計師捕獲或傳播它(第一條的反面表述)。
  3. 支援Unchecked異常:
    沿呼叫棧向上傳播的Checked異常破壞了頂層的方法,因為這些方法必須宣告丟擲所有它們呼叫的方法丟擲的異常。
  4. 支援Checked異常:
    當方法不宣告它們會丟擲何種異常時,就難以處理它們丟擲的異常。
  5. 支援Unchecked異常:
    Check異常的丟擲作為方法介面的一部分,這使得新增或移除早期版本中方法的異常難以實現。

上述每一個觀點都有相反的觀點,下面我會詳細討論這些觀點。

觀點1(支援Checked異常):

編譯器強制檢查,checked異常必須被捕獲或者傳播,這樣就不會忘記處理異常。

相反觀點:

當被強制捕獲或傳播許多異常時,開發人員的效率會受到影響,也可能會只寫

  1. try{  
  2.    callMethodThatThrowsException();  
  3. catch(Exception e){  
  4. }  
來忽略錯誤(糊弄了事)。

觀點2(支援Checked異常):

Unchecked異常容易忘記處理,由於編譯器不強制程式設計師捕獲或傳播它(第一條的反面表述)。

相反觀點1:

強制處理或傳播checked異常導致的草率地異常處理非常糟糕。

相反觀點2:

在近期的一個大型專案中我們決定採用unchecked異常。我在這個專案中獲得的經驗是:使用unchecked異常時,任何方法都可能丟擲異常。因此我不論在寫哪一部分程式碼都時刻注意異常。而不只是聲明瞭checked異常的地方。

此外,許多沒有宣告任何checked異常的標準的Java API方法會丟擲諸如NullPointerException或者InvalidArgumentException之類的unchecked異常。你的應用程式需要處理這些unchecked異常。你可能會說checked異常的存在讓我們容易忘記處理unchecked異常,因為unchecked異常沒有顯式地宣告。

觀點3(支援Unchecked異常):

沿呼叫棧向上傳播的Checked異常破壞了頂層的方法,因為這些方法必須宣告丟擲所有它們呼叫的方法丟擲的異常。即,宣告的異常聚合了呼叫棧中所有的方法丟擲的異常。例如:

  1. publiclong readNumberFromUrl(String url)  
  2. throws BadUrlExceptions, BadNumberException{  
  3.     String data = readDataFromUrl(url);  
  4.     long number = convertData(data);  
  5.     return number;  
  6. }  
  7. private String readDataFromUrl(String url)  
  8. throws BadUrlException {  
  9.    //throw BadUrlException if url is bad.
  10.    //read data and return it.
  11. }  
  12. privatelong convertData(String data)  
  13. throws BadNumberException{  
  14.     //convert data to long.
  15.     //throw BadNumberException if number isn't within valid range.
  16. }  
readNumberFromUrl()必須宣告丟擲BadUrlException和BadNumberException,而這兩個異常是readNumberFromUrl()呼叫的readDataFromUrl() 和 converData()方法丟擲的異常。可以想象一個有數千個類的應用程式的頂層方法需要宣告多少異常。這使得checked異常傳播是一件非常痛苦的事。

相反觀點1:

異常宣告傳播聚合在實際應用程式中很少發生。開發人員時常使用異常包裝機制來優化。如下:

  1. publicvoid readNumberFromUrl(String url)  
  2. throws ApplicationException{  
  3.     try{  
  4.         String data = readDataFromUrl(url);  
  5.         long number = convertData(data);  
  6.     } catch (BadUrlException e){  
  7.         thrownew ApplicationException(e);  
  8.     } catch (BadNumberException e){  
  9.         thrownew ApplicationException(e);  
  10.     }  
  11. }  
readNumberFromUrl()方法只需要宣告丟擲ApplicationException即可。BadUrlException和BadNumberException被捕獲幷包裝進一個更通用的ApplicationException中。通過異常包裝就可以避免異常宣告聚合。

我的個人觀點是,如果你只是包裝異常但並不提供更多資訊,那為什麼要包裝它呢?try-catch塊就成了多餘的程式碼,沒有做任何有意義的事。只需將ApplicationException,BadUrlException和BadNumberException定義為unchecked異常。下面是上述程式碼的unchecked版本:

  1. publicvoid readNumberFromUrl(String url){  
  2.     String data = readDataFromUrl(url);  
  3.     long number = convertData(data);  
  4. }  
也可以包裝unchecked異常。下面是unchecked程式碼的包裝版本。注意readNumberFromUrl()方法不宣告丟擲ApplicationException,即使它可能丟擲該異常。
  1. publicvoid readNumberFromUrl(String url)  
  2.     try{  
  3.         String data = readDataFromUrl(url);  
  4.         long number = convertData(data);  
  5.     } catch (BadUrlException e){  
  6.         thrownew ApplicationException(  
  7.             "Error reading number from URL", e);  
  8.     } catch (BadNumberException e){  
  9.         thrownew ApplicationException(  
  10.             "Error reading number from URL", e);  
  11.     }  
  12. }  

相反觀點2:

另一種常用於避免異常宣告聚集的技術是建立一個應用程式基礎異常類。應用程式中丟擲的所有異常必須是基礎異常類的子類。所有丟擲異常的方法只需宣告丟擲基礎異常。比如一個丟擲Exception的方法可能丟擲Exception的任何子類。如下程式碼:

  1. publiclong readNumberFromUrl(String url)  
  2. throws ApplicationException {  
  3.     String data = readDataFromUrl(url);  
  4.     long number = convertData(data);  
  5.     return number;  
  6. }  
  7. private String readDataFromUrl(String url)  
  8. throws BadUrlException {  
  9.    //throw BadUrlException if url is bad.
  10.    //read data and return it.
  11. }  
  12. privatelong convertData(String data)  
  13. throws BadNumberException{  
  14.     //convert data to long.
  15.     //throw BadNumberException if number isn't within valid range.
  16. }  
  17. publicclass ApplicationException extends Exception{ }  
  18. publicclass BadNumberException   extends ApplicationException{}  
  19. publicclass BadUrlException      extends ApplicationException{}  
注意BadNumberException和BadUrlException不再被宣告丟擲,也不再被捕獲,也沒有包裝。它們是ApplicationException的子類,因此它們會沿著呼叫棧向上傳播。

我還是支援異常包裝:如果應用程式的所有方法都宣告丟擲ApplicationException(基礎異常),為什麼不直接將ApplicationException定義為unchecked?這樣不但省去了一些try-catch塊,也省去了throws語句。

觀點4(支援Checked