JAVA 基礎之 異常機制


 參考地址 

 http://swiftlet.net/archives/998

異常結構樹 :

在Java中,異常分為受檢查的異常,與運行時異常. 兩者都在異常類層次結構中.
下面的圖展示了Java異常類的繼承關系.

圖1

粉紅色的是受檢查的異常(checked exceptions),其必須被 try{}catch語句塊所捕獲,或者在方法簽名里通過throws子句聲明.受檢查的異常必須在編譯時被捕捉處理,命名為 CHecked Exception 是因為Java編譯器要進行檢查,Java虛擬機也要進行檢查,以確保這個規則得到遵守.
綠色的異常是運行時異常(runtime exceptions),需要程序員自己分析代碼決定是否捕獲和處理,比如 空指針,被0除...
而聲明為Error的,則屬於嚴重錯誤,需要根據業務信息進行特殊處理,Error不需要捕捉。

  

 


 

非RuntimeException(checkedException)和RuntimeException(uncheckedException)區別

 ① RuntimeException:RuntimeException體系包括錯誤的類型轉換、數組越界訪問和試圖訪問空指針等等。處理RuntimeException的原則是:如果出現RuntimeException,那么一定是程序員的錯誤。例如,可以通過檢查數組下標和數組邊界來避免數組越界訪問異常。 也可以主動拋出異常,停止程序運行。if(obj==null){throw new exception};......


②其他非RuntimeException(IOException等等):這類異常一般是外部錯誤,例如試圖從文件尾后讀取數據等,這並不是程序本身的錯誤,而是在應用環境中出現的外部錯誤。

1. checkedException 在程序中必須使用try...catch進行處理。

比如一個IO系統的設計者會認為諸如物理文件不存在或者介質無法讀取等異常時很可能發生,而使用者完全可能捕獲這個異常,通過讓用戶 重新輸入文件名等方式重新進行這個操作,也就是說,這是一個可恢復的操作。所以我會在諸如 read()/write()等操作中throw 一個 IOException(checked exception)。同時還要在Finally中關閉連接,數據庫連接同理。(記得File 如果沒有找到,然后file.close()會拋異常,所以要先判斷file是否為null)

 

 

2. RuntimeException可以不使用try...catch進行處理但是如果有異常產生則異常將由JVM進行處理。

對於RuntimeException的子類最好也使用異常處理機制。雖然RuntimeException的異常可以不使用try...catch進行處理但是如果一旦發生異常則肯定會導致程序中斷執行所以為了保證程序再出錯后依然可以執行在開發代碼時最好使用try...catch的異常處理機制進行處理

RuntimeException也可以說是程序員的錯誤,程序員可以通過優秀的代碼編寫,來避免runtimeException.


運行時異常,我們可以不處理。當出現這樣的異常時,總是由虛擬機接管。而Exception是指出現錯誤后,可以由程序員進行補救或其他工作。

 


以下是對JAVA異常的繼承機制的一些總結。

 1. 異常機制 
      異常機制是指當程序出現錯誤后,程序如何處理。具體來說,異常機制提供了程序退出的安全通道。當出現錯誤后,程序執行的流程發生改變,程序的控制權轉移到異常處理器。

      傳統的處理異常的辦法是,函數返回一個特殊的結果來表示出現異常(通常這個特殊結果是大家約定俗稱的),調用該函數的程序負責檢查並分析函數返回的結果。這樣做有如下的弊端:例如函數返回-1代表出現異常,但是如果函數確實要返回-1這個正確的值時就會出現混淆;可讀性降低,將程序代碼與處理異常的代碼混爹在一起;由調用函數的程序來分析錯誤,這就要求客戶程序員對庫函數有很深的了解。
異常處理的流程:
① 遇到錯誤,方法立即結束,並不返回一個值;同時,拋出一個異常對象 。
② 調用該方法的程序也不會繼續執行下去,而是搜索一個可以處理該異常的異常處理器,並執行其中的代碼 。

2 異常的分類 
異常的分類:
① 異常的繼承結構:基類為Throwable,Error和Exception繼承Throwable,RuntimeException和IOException等繼承Exception,具體的RuntimeException繼承RuntimeException。 
② Error和RuntimeException及其子類成為未檢查異常(unchecked),其它異常成為已檢查異常(checked)。


每個類型的異常的特點 
Error體系 :
      Error類體系描述了Java運行系統中的內部錯誤以及資源耗盡的情形。應用程序不應該拋出這種類型的對象(一般是由虛擬機拋出)。如果出現這種錯誤,除了盡力使程序安全退出外,在其他方面是無能為力的。所以,在進行程序設計時,應該更關注Exception體系。

Exception體系包括RuntimeException體系和其他非RuntimeException的體系 :

① RuntimeException:RuntimeException體系包括錯誤的類型轉換、數組越界訪問和試圖訪問空指針等等。處理RuntimeException的原則是:如果出現RuntimeException,那么一定是程序員的錯誤。例如,可以通過檢查數組下標和數組邊界來避免數組越界訪問異常。 
②其他非RuntimeException(IOException等等):這類異常一般是外部錯誤,例如試圖從文件尾后讀取數據等,這並不是程序本身的錯誤,而是在應用環境中出現的外部錯誤。

 
與C++異常分類的不同 :
① Java中RuntimeException這個類名起的並不恰當,因為任何異常都是運行時出現的。(在編譯時出現的錯誤並不是異常,換句話說,異常就是為了解決程序運行時出現的的錯誤)。 
② C++中logic_error與Java中的RuntimeException是等價的,而runtime_error與Java中非RuntimeException類型的異常是等價的。 
 
3 異常的使用方法 
聲明方法拋出異常 
① 語法:throws(略) 
② 為什么要聲明方法拋出異常? 
      方法是否拋出異常與方法返回值的類型一樣重要。假設方法拋出異常確沒有聲明該方法將拋出異常,那么客戶程序員可以調用這個方法而且不用編寫處理異常的代碼。那么,一旦出現異常,那么這個異常就沒有合適的異常控制器來解決。 
③ 為什么拋出的異常一定是已檢查異常? 
      RuntimeException與Error可以在任何代碼中產生,它們不需要由程序員顯示的拋出,一旦出現錯誤,那么相應的異常會被自動拋出。而已檢查異常是由程序員拋出的,這分為兩種情況:客戶程序員調用會拋出異常的庫函數(庫函數的異常由庫程序員拋出);客戶程序員自己使用throw語句拋出異常。遇到Error,程序員一般是無能為力的;遇到RuntimeException,那么一定是程序存在邏輯錯誤,要對程序進行修改(相當於調試的一種方法);只有已檢查異常才是程序員所關心的,程序應該且僅應該拋出或處理已檢查異常。 
      注意:覆蓋父類某方法的子類方法不能拋出比父類方法更多的異常,所以,有時設計父類的方法時會聲明拋出異常,但實際的實現方法的代碼卻並不拋出異常,這樣做的目的就是為了方便子類方法覆蓋父類方法時可以拋出異常。

如何拋出異常 
① 語法:throw(略) 
② 拋出什么異常?對於一個異常對象,真正有用的信息時異常的對象類型,而異常對象本身毫無意義。比如一個異常對象的類型是ClassCastException,那么這個類名就是唯一有用的信息。所以,在選擇拋出什么異常時,最關鍵的就是選擇異常的類名能夠明確說明異常情況的類。 
③ 異常對象通常有兩種構造函數:一種是無參數的構造函數;另一種是帶一個字符串的構造函數,這個字符串將作為這個異常對象除了類型名以外的額外說明。 
④ 創建自己的異常:當Java內置的異常都不能明確的說明異常情況的時候,需要創建自己的異常。需要注意的是,唯一有用的就是類型名這個信息,所以不要在異常類的設計上花費精力。

捕獲異常 
      如果一個異常沒有被處理,那么,對於一個非圖形界面的程序而言,該程序會被中止並輸出異常信息;對於一個圖形界面程序,也會輸出異常的信息,但是程序並不中止,而是返回用錯誤頁面。
      語法:try、catch和finally(略),控制器模塊必須緊接在try塊后面。若擲出一個異常,異常控制機制會搜尋參數與異常類型相符的第一個控制器隨后它會進入那個catch 從句,並認為異常已得到控制。一旦catch 從句結束對控制器的搜索也會停止。 
      捕獲多個異常(注意語法與捕獲的順序)(略) 
      finally的用法與異常處理流程(略) 
      異常處理做什么?對於Java來說,由於有了垃圾收集,所以異常處理並不需要回收內存。但是依然有一些資源需要程序員來收集,比如文件、網絡連接和圖片等資源。 
      應該聲明方法拋出異常還是在方法中捕獲異常?原則:捕捉並處理哪些知道如何處理的異常,而傳遞哪些不知道如何處理的異常。
再次拋出異常 
①為什么要再次拋出異常? 在本級中,只能處理一部分內容,有些處理需要在更高一級的環境中完成,所以應該再次拋出異常。這樣可以使每級的異常處理器處理它能夠處理的異常。 
②異常處理流程 :對應與同一try塊的catch塊將被忽略,拋出的異常將進入更高的一級。 
 
4 關於異常的其他問題 
① 過度使用異常 :首先,使用異常很方便,所以程序員一般不再願意編寫處理錯誤的代碼,而僅僅是簡簡單單的拋出一個異常。這樣做是不對的,對於完全已知的錯誤,應該編寫處理這種錯誤的代碼,增加程序的魯棒性。另外,異常機制的效率很差。 
② 將異常與普通錯誤區分開:對於普通的完全一致的錯誤,應該編寫處理這種錯誤的代碼,增加程序的魯棒性。只有外部的不能確定和預知的運行時錯誤才需要使用異常。 
③ 異常對象中包含的信息 :一般情況下,異常對象唯一有用的信息就是類型信息。但使用異常帶字符串的構造函數時,這個字符串還可以作為額外的信息。調用異常對象的getMessage()、toString()或者printStackTrace()方法可以分別得到異常對象的額外信息、類名和調用堆棧的信息。並且后一種包含的信息是前一種的超集。

 


 

常見的RuntimeException- - RuntimeException是開發中最容易遇到的下面列舉一下常見的RuntimeException

1、NullPointerException見的最多了其實很簡單一般都是在null對象上調用方法了。

String s=null; booleaneq=s.equals("");

// NullPointerException 這里你看的非常明白了為什么一到程序中就暈呢

public intgetNumber(String str){   if(str.equals("A")) return 1;    else if(str.equals("B")) return 2; }

這個方法就有可能拋出NullPointerException,我建議你主動拋出異常因為代碼一多你可能又暈了。

public intgetNumber(String str){   if(str==null) throw new NullPointerException("參數不能為空");

//你是否覺得明白多了  

if(str.equals("A")) return 1;else if(str.equals("B")) return 2; }

 

2、NumberFormatException繼承IllegalArgumentException字符串轉換為數字時出現。比如inti= Integer.parseInt("ab3");

3、ArrayIndexOutOfBoundsException:數組越界。比如int[] a=new int[3]; int b=a[3];

4、StringIndexOutOfBoundsException字符串越界。比如 String s="hello"; char c=s.chatAt(6);

5、ClassCastException:類型轉換錯誤。比如 Object obj=new Object(); String s=(String)obj;

6、UnsupportedOperationException:該操作不被支持。如果我們希望不支持這個方法可以拋出這個異常。既然不支持還要這個干嗎有可能子類中不想支持父類中有的方法可以直接拋出這個異常。

7、ArithmeticException算術錯誤典型的就是0作為除數的時候。

8、IllegalArgumentException非法參數,在把字符串轉換成數字的時候經常出現的一個異常,我們可以在自己的程序中好好利用這個異常.尤其是傳入參數是null或者非法的情況下,判斷后主動拋出新異常,並添加聲明(unchecked exception , 需要主動添加聲明)

 


異常處理原則 :

http://cwfmaker.iteye.com/blog/1415737

 


Java異常處理實例分析--六種異常處理的陋習

 

1.簡單的處理異常,直接printstack

4.在異常處理模塊中提供適量的錯誤原因信息,組織錯誤信息使其易於理解和閱讀。

2.不指定具體的異常

3.保證所有資源都被正確釋放。充分運用finally關鍵詞。

5.過於龐大的try塊

6.輸出數據不完整(只要有數據輸出或者寫文件,一定要特別考慮異常發生會導致的結果)

 

你覺得自己是一個Java專家嗎?是否肯定自己已經全面掌握了Java的異常處理機制?在下面這段代碼中,你能夠迅速找出異常處理的六個問題嗎?

1 OutputStreamWriter out = ...
2 java.sql.Connection conn = ...
3 try { //
4  Statement stat = conn.createStatement();
5  ResultSet rs = stat.executeQuery(
6   "select uid, name from user");
7  while (rs.next())
8  {
9   out.println("ID:" + rs.getString("uid") //
10    ",姓名:" + rs.getString("name"));
11  }
12  conn.close(); //
13  out.close();
14 }
15 catch(Exception ex) //
16 {
17  ex.printStackTrace(); //⑴,⑷
18 }

 


  作為一個Java程序員,你至少應該能夠找出兩個問題。但是,如果你不能找出全部六個問題,請繼續閱讀本文。

本文討論的不是Java異常處理的一般性原則,因為這些原則已經被大多數人熟知。我們要做的是分析各種可稱為“反例”(anti-pattern)的違背優秀編碼規范的常見壞習慣,幫助讀者熟悉這些典型的反面例子,從而能夠在實際工作中敏銳地察覺和避免這些問題。

反例之一:丟棄異常

代碼:15行-18行。

這段代碼捕獲了異常卻不作任何處理,可以算得上Java編程中的殺手。從問題出現的頻繁程度和禍害程度來看,它也許可以和C/C++程序的一個惡名遠播的問題相提並論??不檢查緩沖區是否已滿。如果你看到了這種丟棄(而不是拋出)異常的情況,可以百分之九十九地肯定代碼存在問題(在極少數情況下,這段代碼有存在的理由,但最好加上完整的注釋,以免引起別人誤解)。

這段代碼的錯誤在於,異常(幾乎)總是意味着某些事情不對勁了,或者說至少發生了某些不尋常的事情,我們不應該對程序發出的求救信號保持沉默和無動於衷。調用一下printStackTrace算不上“處理異常”。不錯,調用printStackTrace對調試程序有幫助,但程序調試階段結束之后,printStackTrace就不應再在異常處理模塊中擔負主要責任了。

丟棄異常的情形非常普遍。打開JDK的ThreadDeath類的文檔,可以看到下面這段說明:“特別地,雖然出現 ThreadDeath是一種‘正常的情形’,但ThreadDeath類是Error而不是Exception的子類,因為許多應用會捕獲所有的 Exception然后丟棄它不再理睬。”這段話的意思是,雖然ThreadDeath代表的是一種普通的問題,但鑒於許多應用會試圖捕獲所有異常然后不予以適當的處理,所以JDK把ThreadDeath定義成了Error的子類,因為Error類代表的是一般的應用不應該去捕獲的嚴重問題。可見,丟棄異常這一壞習慣是如此常見,它甚至已經影響到了Java本身的設計。

那么,應該怎樣改正呢?主要有四個選擇:

1、處理異常。針對該異常采取一些行動,例如修正問題、提醒某個人或進行其他一些處理,要根據具體的情形確定應該采取的動作。再次說明,調用printStackTrace算不上已經“處理好了異常”。

2、重新拋出異常。處理異常的代碼在分析異常之后,認為自己不能處理它,重新拋出異常也不失為一種選擇。

3、把該異常轉換成另一種異常。大多數情況下,這是指把一個低級的異常轉換成應用級的異常(其含義更容易被用戶了解的異常)。

4、不要捕獲異常。

結論一:既然捕獲了異常,就要對它進行適當的處理。不要捕獲異常之后又把它丟棄,不予理睬。

反例之二:不指定具體的異常

代碼:15行。

許多時候人們會被這樣一種“美妙的”想法吸引:用一個catch語句捕獲所有的異常。最常見的情形就是使用catch(Exception ex)語句。但實際上,在絕大多數情況下,這種做法不值得提倡。為什么呢?

要理解其原因,我們必須回顧一下catch語句的用途。catch語句表示我們預期會出現某種異常,而且希望能夠處理該異常。異常類的作用就是告訴 Java編譯器我們想要處理的是哪一種異常。由於絕大多數異常都直接或間接從java.lang.Exception派生,catch (Exception ex)就相當於說我們想要處理幾乎所有的異常。

再來看看前面的代碼例子。我們真正想要捕獲的異常是什么呢?最明顯的一個是SQLException,這是JDBC操作中常見的異常。另一個可能的異常是IOException,因為它要操作 OutputStreamWriter。顯然,在同一個catch塊中處理這兩種截然不同的異常是不合適的。如果用兩個catch塊分別捕獲 SQLException和IOException就要好多了。這就是說,catch語句應當盡量指定具體的異常類型,而不應該指定涵蓋范圍太廣的 Exception類。

另一方面,除了這兩個特定的異常,還有其他許多異常也可能出現。例如,如果由於某種原因, executeQuery返回了null,該怎么辦?答案是讓它們繼續拋出,即不必捕獲也不必處理。實際上,我們不能也不應該去捕獲可能出現的所有異常,程序的其他地方還有捕獲異常的機會??直至最后由JVM處理。

結論二:在catch語句中盡可能指定具體的異常類型,必要時使用多個catch。不要試圖處理所有可能出現的異常。

反例之三:占用資源不釋放

代碼:3行-14行。

異常改變了程序正常的執行流程。這個道理雖然簡單,卻常常被人們忽視。如果程序用到了文件、Socket、JDBC連接之類的資源,即使遇到了異常,也要正確釋放占用的資源。為此,Java提供了一個簡化這類操作的關鍵詞finally。

finally是樣好東西:不管是否出現了異常,Finally保證在try/catch/finally塊結束之前,執行清理任務的代碼總是有機會執行。遺憾的是有些人卻不習慣使用finally。

當然,編寫finally塊應當多加小心,特別是要注意在finally塊之內拋出的異常??這是執行清理任務的最后機會,盡量不要再有難以處理的錯誤。

結論三:保證所有資源都被正確釋放。充分運用finally關鍵詞。

反例之四:不說明異常的詳細信息

  代碼:3行-18行。

仔細觀察這段代碼:如果循環內部出現了異常,會發生什么事情?我們可以得到足夠的信息判斷循環內部出錯的原因嗎?不能。我們只能知道當前正在處理的類發生了某種錯誤,但卻不能獲得任何信息判斷導致當前錯誤的原因。

printStackTrace的堆棧跟蹤功能顯示出程序運行到當前類的執行流程,但只提供了一些最基本的信息,未能說明實際導致錯誤的原因,同時也不易解讀。

因此,在出現異常時,最好能夠提供一些文字信息,例如當前正在執行的類、方法和其他狀態信息,包括以一種更適合閱讀的方式整理和組織printStackTrace提供的信息。

結論四:在異常處理模塊中提供適量的錯誤原因信息,組織錯誤信息使其易於理解和閱讀。

反例之五:過於龐大的try塊

代碼:3行-14行。

經常可以看到有人把大量的代碼放入單個try塊,實際上這不是好習慣。這種現象之所以常見,原因就在於有些人圖省事,不願花時間分析一大塊代碼中哪幾行代碼會拋出異常、異常的具體類型是什么。把大量的語句裝入單個巨大的try塊就象是出門旅游時把所有日常用品塞入一個大箱子,雖然東西是帶上了,但要找出來可不容易。

一些新手常常把大量的代碼放入單個try塊,然后再在catch語句中聲明Exception,而不是分離各個可能出現異常的段落並分別捕獲其異常。這種做法為分析程序拋出異常的原因帶來了困難,因為一大段代碼中有太多的地方可能拋出Exception。

結論五:盡量減小try塊的體積。

反例之六:輸出數據不完整

代碼:7行-11行。

不完整的數據是Java程序的隱形殺手。仔細觀察這段代碼,考慮一下如果循環的中間拋出了異常,會發生什么事情。循環的執行當然是要被打斷的,其次, catch塊會執行??就這些,再也沒有其他動作了。已經輸出的數據怎么辦?使用這些數據的人或設備將收到一份不完整的(因而也是錯誤的)數據,卻得不到任何有關這份數據是否完整的提示。對於有些系統來說,數據不完整可能比系統停止運行帶來更大的損失。

較為理想的處置辦法是向輸出設備寫一些信息,聲明數據的不完整性;另一種可能有效的辦法是,先緩沖要輸出的數據,准備好全部數據之后再一次性輸出。

結論六:全面考慮可能出現的異常以及這些異常對執行流程的影響。

改寫后的代碼

根據上面的討論,下面給出改寫后的代碼。也許有人會說它稍微有點?嗦,但是它有了比較完備的異常處理機制。

OutputStreamWriter out = ...
java.sql.Connection conn = ...
try {
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery(
"select uid, name from user");
while (rs.next())
{
out.println("ID:" + rs.getString("uid") + ",姓名: " + rs.getString("name"));
}
}
catch(SQLException sqlex)
{
out.println("警告:數據不完整");
throw new ApplicationException("讀取數據時出現SQL錯誤", sqlex);
}
catch(IOException ioex)
{
throw new ApplicationException("寫入數據時出現IO錯誤", ioex);
}
finally
{
if (conn != null) {
try {
conn.close();
}
catch(SQLException sqlex2)
{
System.err(this.getClass().getName() + ".mymethod - 不能關閉數據庫連接: " + sqlex2.toString());
}
}

if (out != null) {
try {
out.close();
}
catch(IOException ioex2)
{
System.err(this.getClass().getName() + ".mymethod - 不能關閉輸出文件" + ioex2.toString());
}
}
}

  本文的結論不是放之四海皆准的教條,有時常識和經驗才是最好的老師。如果你對自己的做法沒有百分之百的信心,務必加上詳細、全面的注釋。

另一方面,不要笑話這些錯誤,不妨問問你自己是否真地徹底擺脫了這些壞習慣。即使最有經驗的程序員偶爾也會誤入歧途,原因很簡單,因為它們確確實實帶來了“方便”。所有這些反例都可以看作Java編程世界的惡魔,它們美麗動人,無孔不入,時刻誘惑着你。也許有人會認為這些都屬於雞皮蒜毛的小事,不足掛齒,但請記住:勿以惡小而為之,勿以善小而不為。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM