一、引言 異常總是不可避免的,就算我們自身的代碼足夠優秀,但卻不能保證用戶都按照我們想法進行輸入,就算用戶按照我們的想法進行輸入,我們也不能保證操作系統穩定,另外還有網路環境等,不可控因素太多,異常也不可避免。 但我們可以通過異常處理機制讓程式有更好的容錯性和相容性,當程式出現異常時,系統自動生成E ...
一、引言
異常總是不可避免的,就算我們自身的代碼足夠優秀,但卻不能保證用戶都按照我們想法進行輸入,就算用戶按照我們的想法進行輸入,我們也不能保證操作系統穩定,另外還有網路環境等,不可控因素太多,異常也不可避免。
但我們可以通過異常處理機制讓程式有更好的容錯性和相容性,當程式出現異常時,系統自動生成Exception對象通知系統,從而將業務功能實現代碼和錯誤處理代碼分離。
異常處理已經成為衡量一門語言是否成熟的標誌之一,增加了異常處理機制後程式有更好的健壯性和容錯性。
二、
try{ //業務代碼 } catch(IOException ex){ //錯誤處理 } catch(Exception ex){ //錯誤處理代碼 }
當try塊代碼出錯時,系統生成一個異常對象,並將對象拋給運行環境,這個過程叫做拋出異常,運行環境接收到異常對象是,會尋找處理該異常對象的catch代碼塊,找到合適的catch塊,就將對象給其處理,如果找不到,則運行環境終止,程式也將退出。
2.2 異常類繼承體系
Java提供了豐富的異常類,這些異常類有嚴格的繼承關係
從這個圖可以看出異常主要分為兩類,Error與Exception,Error錯誤一般是指與虛擬機相關的問題,如系統崩潰、虛擬機錯誤,這些錯誤無法恢復或不可能捕獲,將導致應用程式崩潰,這些不需要我們去捕獲。
在捕獲異常時我們通常把Exception類放在最後,因為按照異常捕獲的機制,從上至下判斷該異常對象是否是catch中的異常類或其異常子類,一旦比較成功則用此catch進行處理。如果將Exception類放在前面,那麼就會進行直接進入其中,因為Exception類是所有異常類的父類,那排在它後面的異常類將永遠得不到執行的機會,這種機制我們稱為先小後大。
2.3 多異常捕獲
Java 7 開始,一個catch塊中可以捕獲多種類型的異常:
public static void main(String[] args) { try { Integer a = Integer.parseInt(args[0]); Integer b = Integer.parseInt(args[1]); Integer c = a / b; System.out.println(c); } catch (IndexOutOfBoundsException | NumberFormatException | ArithmeticException ie) { //異常變數預設final,不能重新賦值 ie = new ArithmeticException("text"); } catch (Exception ex) { } }
多個異常之間用豎線(|)隔開,並且異常變數預設final,不能重新賦值。
2.4 獲取異常信息
異常捕獲後我們想要查看異常信息,可以通過catch後的異常形參來獲得,常用的方法如下:
-
getMessage():返回異常的詳細描述字元串。
-
getStackTrace():返回異常跟蹤棧信息。
-
printStackTrace():將異常跟蹤棧信息按照標準格式輸出。
-
printStackTrace(PrintStream p):將異常跟蹤棧信息輸出到指定輸出流。
try{ //業務代碼 }catch(XXXException xx){ //異常處理 }catch(XXXException xx){ }finally{ //資源回收 }
在異常處理中,try是必須的,沒有try塊,後面的catch和finally沒有意義,catch和finally必須出現一個,finally塊必須是最後。
如果try塊中有return語句,則會先執行finally,然後再執行return語句,如果try塊中有exit語句,則不會執行finally,都直接退出虛擬機了當然不會再去執行。
2.
try ( BufferedReader bufferedReader = new BufferedReader(new FileReader("")) ) { bufferedReader.read(); }
三、
Java的異常分為兩大類:Checked(可檢查)異常和Runtime(運行時)異常,所有的RuntimeException類及其子類的實例就是Runtime異常,其他的都是Checked異常。
對於Checked異常處理方式有兩種,一種是明確知道如何處理該異常,用try catch來捕獲異常,然後在catch中修複異常,一種是不知道如何處理,在定義方法時申明拋出異常。
Runtime異常無需顯示申明拋出,需要捕獲異常,就用try catch來實現。
使用throws聲明的思路是:當前方法不知道如何處理這種類型的異常,則由上一級調用者處理,如果main方法也不知道如何處理,也可以使用throws拋給JVM,JVM的處理是,列印異常的跟蹤棧信息,並終止程式。
throws聲明拋出只能在方法簽名中使用,可以聲明拋出多個異常類,多個異常類用逗號隔開,如:
public static void main(String[] args) throws IOException { FileInputStream fileInputStream = new FileInputStream(""); }
申明瞭throws就不需要再使用try catch來捕獲異常了。
如果某段代碼中調用了一個帶throws聲明的方法,那麼必須用try catch來處理或者也帶throws聲明,如下例子:
public static void main(String[] args) { try { test(); } catch (IOException e) { e.printStackTrace(); } } public static void test () throws IOException{ FileInputStream fileInputStream = new FileInputStream(""); }
這個時候要註意,子類方法聲明拋出的異常應該是父類方法聲明拋出異常的子類活相同,不允許比父類聲明拋出的異常多。
程式出現錯誤,系統會拋出異常,有時候我們也想自行拋出異常,比如用戶未登錄,我們可能就直接拋出錯誤,這種自行拋出的異常一般都與業務相關,因為業務數據與既定不符,但是這種異常並不是一種錯誤,系統不會捕捉,就需要我們自行拋出。
使用throw語句進行異常拋出,拋出的不是一個異常類,而是一個異常實例,而且每次只能拋出一個:
if (user== null) { throw new Exception("用戶不存在"); }
這裡我們又要區分Checked異常與運行時異常,運行時異常申明非常簡單,直接拋出即可,而Checked異常又要像之前一樣,要麼使用try catch,要麼聲明throws
public static void main(String[] args) { try { //檢查時異常需要寫try catch test1(); } catch (Exception e) { e.printStackTrace(); } //運行時異常直接調用即可 test2(); } public static void test1() throws Exception { if (1 > 0) { throw new Exception("用戶不存在"); } } public static void test2() { if (1 > 0) { throw new RuntimeException("用戶不存在"); } }
public class GlobalException extends RuntimeException { //無參構造器 public GlobalException() { } //帶有錯誤描述信息的構造器 public GlobalException(String msg) { super(msg); } }
在實際開發中,我們一般會分層開發,比較常用的是三層,表現出、業務邏輯層、資料庫訪問層,我們不會拋出資料庫異常給用戶,因為這些異常中有堆棧信息,很不安全,也非常的不友好。
通常,我們捕獲原始異常(可以寫入日誌),然後再拋出一個業務異常(通常是自定義的異常),這個業務異常可以提示用戶異常的原因:
public void update() throws GlobalException{ try{ //執行sql } catch (SQLException ex){ //記錄日誌 ... //拋出自定義錯誤 throw new GlobalException("資料庫報錯"); } catch (Exception ex){ //記錄日誌 ... throw new GlobalException("未知錯誤!"); } }
這種捕獲一個異常然後拋出另一個異常,並將原始信息保存起來的是一種典型的鏈式處理(責任鏈模式)。
五、
異常給系統帶來了健壯性和容錯性,但是使用異常處理並非如此簡單,我們還要註意性能和結構的優化,有些規則我們必須瞭解,而這些規則的主要目標是:
-
程式代碼混亂最小化。
-
捕獲並保留診斷信息。
-
通知合適的人員
-
採用合適的方式結束異常。
5.1 不要過度使用異常
什麼叫過度使用異常呢?有兩種情況,一是把異常和普通錯誤放在一起,使用異常來代替錯誤,什麼意思呢?就是對一些我們已知或可控的錯誤進行異常處理,如一些業務邏輯判斷,用戶的輸入等,並不是只有直接拋出異常這種選擇,我們可以直接通過業務處理進行錯誤返回,而不是拋出錯誤,拋出錯誤的效率要低一些,只有對外部的、不能確定和預知的運行時錯誤使用異常。
二就是使用異常來代替流程式控制制,異常處理機制的初衷是將不可以預期的錯誤和正常的業務代碼分離,不應該用異常來進行流程式控制制。
5.2 不要使用過大的try塊
不要把大量的業務代碼放在try中,大量的業務代碼意味著錯誤可能性也增大,也意味著一旦出錯,分析錯誤的複雜度也增加,而且try中包含大量業務,可能後面緊跟的catch塊也很多,我們會使用多個catch來捕獲錯誤,這樣代碼也很臃腫,應該儘量細分try,去分別捕獲並處理。
5.3 不要忽略捕獲到的異常
不要忽略異常,當我們捕獲到異常時,我們不要去忽略它,如果在catch中什麼也不做,那是一種恐怖的做法,因為這意味著出現了錯誤我們並不知道(極特殊的情況例外,比如:一些可重試的業務處理),最起碼的做法是列印錯誤日誌,更進一步看是否可以修複錯誤,或者向上拋出錯誤。