Java異常:選擇Checked Exception還是Unchecked Exception?
原文地址:http://blog.csdn.net/kingzone_2008/article/details/8535287
Java包含兩種異常:checked異常和unchecked異常。C#只有unchecked異常。checked和unchecked異常之間的區別是:
- Checked異常必須被顯式地捕獲或者傳遞,如Basic try-catch-finally Exception Handling一文中所說。而unchecked異常則可以不必捕獲或丟擲。
- Checked異常繼承java.lang.Exception類。Unchecked異常繼承自java.lang.RuntimeException類。
有許多支援或者反對二者甚至是否應該使用checked異常的爭論。本文將討論一些常見的觀點。開始之前,先澄清一個問題:
Checked和unchecked異常從功能的角度來講是等價的。可以用checked異常實現的功能必然也可以用unchecked異常實現,反之亦然。
選擇checked異常還是unchecked異常是個人習慣或者組織規定問題。並不存在誰比誰強大的問題。
一個簡單的例子
在討論checked和unchecked異常的優缺點前先看一下程式碼中如下使用它們。下面是一個丟擲checked異常的方法,另一個方法呼叫了它:
-
publicvoid storeDataFromUrl(String url){
- try {
- String data = readDataFromUrl(url);
- } catch (BadUrlException e) {
- e.printStackTrace();
- }
- }
- public String readDataFromUrl(String url)
- throws BadUrlException{
- if(isUrlBad(url)){
- thrownew BadUrlException("Bad URL: " + url);
- }
-
String data = null
- //read lots of data over HTTP and return
- //it as a String instance.
- return data;
- }
- publicclass BadUrlException extends Exception {
- public BadUrlException(String s) {
- super(s);
- }
- }
- publicvoid storeDataFromUrl(String url)
- throws BadUrlException{
- String data = readDataFromUrl(url);
- }
- publicclass BadUrlException extends RuntimeException {
- public BadUrlException(String s) {
- super(s);
- }
- }
- publicvoid storeDataFromUrl(String url){
- String data = readDataFromUrl(url);
- }
- public String readDataFromUrl(String url) {
- if(isUrlBad(url)){
- thrownew BadUrlException("Bad URL: " + url);
- }
- String data = null;
- //read lots of data over HTTP and
- //return it as a String instance.
- return data;
- }
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異常的列表。
- 支援Checked異常:
編譯器強制檢查,checked異常必須被捕獲或者傳播,這樣就不會忘記處理異常。 - 支援Checked異常:
Unchecked異常容易忘記處理,由於編譯器不強制程式設計師捕獲或傳播它(第一條的反面表述)。 - 支援Unchecked異常:
沿呼叫棧向上傳播的Checked異常破壞了頂層的方法,因為這些方法必須宣告丟擲所有它們呼叫的方法丟擲的異常。 - 支援Checked異常:
當方法不宣告它們會丟擲何種異常時,就難以處理它們丟擲的異常。 - 支援Unchecked異常:
Check異常的丟擲作為方法介面的一部分,這使得新增或移除早期版本中方法的異常難以實現。
上述每一個觀點都有相反的觀點,下面我會詳細討論這些觀點。
觀點1(支援Checked異常):
編譯器強制檢查,checked異常必須被捕獲或者傳播,這樣就不會忘記處理異常。
相反觀點:
當被強制捕獲或傳播許多異常時,開發人員的效率會受到影響,也可能會只寫
- try{
- callMethodThatThrowsException();
- catch(Exception e){
- }
觀點2(支援Checked異常):
Unchecked異常容易忘記處理,由於編譯器不強制程式設計師捕獲或傳播它(第一條的反面表述)。
相反觀點1:
強制處理或傳播checked異常導致的草率地異常處理非常糟糕。
相反觀點2:
在近期的一個大型專案中我們決定採用unchecked異常。我在這個專案中獲得的經驗是:使用unchecked異常時,任何方法都可能丟擲異常。因此我不論在寫哪一部分程式碼都時刻注意異常。而不只是聲明瞭checked異常的地方。
此外,許多沒有宣告任何checked異常的標準的Java API方法會丟擲諸如NullPointerException或者InvalidArgumentException之類的unchecked異常。你的應用程式需要處理這些unchecked異常。你可能會說checked異常的存在讓我們容易忘記處理unchecked異常,因為unchecked異常沒有顯式地宣告。
觀點3(支援Unchecked異常):
沿呼叫棧向上傳播的Checked異常破壞了頂層的方法,因為這些方法必須宣告丟擲所有它們呼叫的方法丟擲的異常。即,宣告的異常聚合了呼叫棧中所有的方法丟擲的異常。例如:
- publiclong readNumberFromUrl(String url)
- throws BadUrlExceptions, BadNumberException{
- String data = readDataFromUrl(url);
- long number = convertData(data);
- return number;
- }
- private String readDataFromUrl(String url)
- throws BadUrlException {
- //throw BadUrlException if url is bad.
- //read data and return it.
- }
- privatelong convertData(String data)
- throws BadNumberException{
- //convert data to long.
- //throw BadNumberException if number isn't within valid range.
- }
相反觀點1:
異常宣告傳播聚合在實際應用程式中很少發生。開發人員時常使用異常包裝機制來優化。如下:
- publicvoid readNumberFromUrl(String url)
- throws ApplicationException{
- try{
- String data = readDataFromUrl(url);
- long number = convertData(data);
- } catch (BadUrlException e){
- thrownew ApplicationException(e);
- } catch (BadNumberException e){
- thrownew ApplicationException(e);
- }
- }
我的個人觀點是,如果你只是包裝異常但並不提供更多資訊,那為什麼要包裝它呢?try-catch塊就成了多餘的程式碼,沒有做任何有意義的事。只需將ApplicationException,BadUrlException和BadNumberException定義為unchecked異常。下面是上述程式碼的unchecked版本:
- publicvoid readNumberFromUrl(String url){
- String data = readDataFromUrl(url);
- long number = convertData(data);
- }
- publicvoid readNumberFromUrl(String url)
- try{
- String data = readDataFromUrl(url);
- long number = convertData(data);
- } catch (BadUrlException e){
- thrownew ApplicationException(
- "Error reading number from URL", e);
- } catch (BadNumberException e){
- thrownew ApplicationException(
- "Error reading number from URL", e);
- }
- }
相反觀點2:
另一種常用於避免異常宣告聚集的技術是建立一個應用程式基礎異常類。應用程式中丟擲的所有異常必須是基礎異常類的子類。所有丟擲異常的方法只需宣告丟擲基礎異常。比如一個丟擲Exception的方法可能丟擲Exception的任何子類。如下程式碼:
- publiclong readNumberFromUrl(String url)
- throws ApplicationException {
- String data = readDataFromUrl(url);
- long number = convertData(data);
- return number;
- }
- private String readDataFromUrl(String url)
- throws BadUrlException {
- //throw BadUrlException if url is bad.
- //read data and return it.
- }
- privatelong convertData(String data)
- throws BadNumberException{
- //convert data to long.
- //throw BadNumberException if number isn't within valid range.
- }
- publicclass ApplicationException extends Exception{ }
- publicclass BadNumberException extends ApplicationException{}
- publicclass BadUrlException extends ApplicationException{}
我還是支援異常包裝:如果應用程式的所有方法都宣告丟擲ApplicationException(基礎異常),為什麼不直接將ApplicationException定義為unchecked?這樣不但省去了一些try-catch塊,也省去了throws語句。