接上篇《JavaSE 基礎學習之三 —— Java 的繼承與接口》
四. JavaSE 基礎學習之四 —— 異常處理
1. 兩種類型的異常
- 運行時異常 (RuntimeException):不處理也能通過編譯,jvm 會幫助處理,也可以自行處理;
- 其他異常:對於其他異常,如果不處理程序就不能通過編譯,必須自己處理;
注:
所有的異常產生之後,都是一個類的實例對象,而且這些異常全部繼承於 Exception;java 中對所有的異常都進行了細緻分類,每種異常都有一個具體的類。
2. 異常處理的關鍵字
(1) try… catch…
- try 後面可以有多個 catch;
- 所有的異常都是 Exception 的子類;
- 多個 catch 的時候,父類的異常一定要寫在子類的後面;
- Exception 可以認爲是萬能異常;
- 常用 Exception 的方法:
- getMessage(): 返回此 throwable 的詳細消息字符串;
- toString():返回此 throwable 的簡短描述。
- printStackTrace():打印異常的堆棧,將此 throwable 及其追蹤輸出至標準錯誤流;
(2) finally
finally 關鍵字後無論有沒有異常,最終都要執行的操作;
例:
public class Demo1 {
public static void main(String[] args) {
try {
int a = Integer.parseInt(args[0]);
int b = Integer.parseInt(args[1]);
System.out.println(a/b);
} catch (ArrayIndexOutOfBoundsException e) {
// TODO: handle exception
System.out.println("數組下標越界");
} catch (ArithmeticException e) {
System.out.println("除數不能爲 0");
} catch (Exception e) {
System.out.println(e.getMessage());
System.out.println(e);
System.out.println("其他異常");
} finally {
System.out.println("無論有沒有異常,最終都要執行的操作");
}
}
}
(3) throw
throw 關鍵字表示在某種情況下,我們自己手動拋出的異常實例;
例:定義一個 Person 類,對其年齡進行設置,如果年齡超過 120,則認爲出現錯誤,手動拋出異常。
public class Person {
private int age = 10;
public int getAge() {
return age;
}
public void setAge(int age) throws Exception{
try {
if(age < 0 || age > 120) {
throw new Exception("年齡有誤");
} else {
this.age = age;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
如果運行了程序 setAge(130),則會拋出“年齡有誤”的異常。
(4) throws
throws 關鍵字在一個方法的方法簽名後面使用,用處在於通過編譯,告訴使用者一旦調用了當前方法,可能出現處理哪些異常,這樣可以令使用者儘量的迴避異常;當你的方法裏拋出了 checked 異常,如你不 catch,代表你當時不處理(不想處理或沒條件處理),但你必須得通過”throws那個異常”告訴系統說,這兒有個問題,我現在不處理,將來一定別人要處理,否則執行到它,系統會”不優雅”的崩潰。
舉個例子,工兵張三發現了地雷,假如他處理完就完事兒了。但是他發現了地雷,自己卻沒帶齊工具,沒法處理,他必須做個標記,說這兒有一個地雷,別的工兵將來一定要處理,否則將來有人踩上去會爆炸。
注意:throws只是標記,並沒處理,程序執行到那裏,系統還是會崩潰!
例:
public class Person1 {
private int age;
// throws 異常,通知此處代碼有問題
public void setAge(int age) throws Exception{
if(age < 0 || age > 120)
throw new Exception("年齡有誤!");
this.age = age;
}
public int getAge() {
return age;
}
}
3. 自定義異常
項目中,我們可以通過創建一個類繼承 jdk 提供的異常類 (RunTimeException 或 Exception),自定義的實現一個異常類。
自定義異常的創建有利有弊:優點如下:
- 工作過程中,項目是分模塊或者分功能開發的,基本不會你一個人開發一整個項目,使用自定義異常類就統一了對外異常展示的方式;
- 有時候我們遇到某些校驗或者問題時,需要直接結束掉當前的請求,這時便可以通過拋出自定義異常來結束。例如,如果你項目中使用了SpringMVC比較新的版本的話有控制器增強,可以通過@ControllerAdvice註解寫一個控制器增強類來攔截自定義的異常並響應給前端相應的信息;
- 系統中有些錯誤是符合Java語法的,但不符合我們項目的業務邏輯,使用自定義異常可以在我們項目中某些特殊的業務邏輯時拋出異常。例如對於性別,”中性”.equals(sex),性別等於中性時我們要拋出異常,而Java是不會有這種異常的;
- 使用自定義異常繼承相關的異常來拋出處理後的異常信息可以隱藏底層的異常,這樣更安全,異常信息也更加直觀。自定義異常可以拋出我們自己想要拋出的信息,可以通過拋出的信息區分異常發生的位置,根據異常名我們就可以知道哪裏有異常,根據異常提示信息進行程序修改。例如,空指針異常NullPointException,我們可以拋出信息爲“xxx爲空”定位異常位置,而不用輸出堆棧信息。
自定義異常的缺點主要在於,發現異常、拋出異常以及處理異常的工作必須靠編程人員在代碼中利用異常處理機制自己完成。這樣就相應的增加了一些開發成本和工作量,所以項目沒必要的話,也不一定非得要用上自定義異常,要能夠自己去權衡。
關於自定義異常,可以參考《Java異常之自定義異常》的博客。
4. 關於異常的編程建議
建議:
- 自己拋出的異常必須要填寫詳細的描述信息,便於問題定位;
- 例如:throw new IOException(“Writing data error! Data: ” + data.toString());
- 在程序中,選擇使用異常處理還是錯誤返回碼處理,應該根據是否有利於程序結構來確定,且不能將異常和錯誤碼混合使用。通常情況下推薦異常處理;
- 記錄異常時,不要只保存 exception.getMessage(),一般可以通過日誌工具記錄完整的異常堆棧信息;
- 一個方法不應該拋出太多類型的異常。如果確實有很多異常類型,首先應該考慮用異常來進行區別。通常來說 throws / exception 子句標明的異常最多不要超過三個;
- 異常捕獲儘量不要直接捕獲 catch(Exception ex),應該把異常細分處理,設計更加合理的異常處理分支;
- public 類型的底層方法需要對輸入參數進行判斷,如果參數不合法,應該主動拋出 RuntimeException;
強烈建議:
- 在調用的最高層,必須處理所有的異常;
- 如果捕獲了異常,然後拋出新的異常,則必須將原異常的信息全部包含在新的異常中;
- 系統非正常運行產生的異常捕獲之後,如果不對該異常進行處理,必須記錄日誌;
- 對於多個異常的額情況,必須對應多個 catch 分別處理,禁止使用一個 catch 處理多個異常。如下例所示:
public void sample() {
try {
// 某個拋出異常塊
}
// 處理異常種類 1
catch (IllegalStateException illEx) {
log.wirte(illEx.printStackTrace());
throw illex;
}
// 處理異常種類 2
catch (SQLException SQLEx) {
log.write(SQLEx.printStackTrace());
throw SQLEx;
}
finally {
// 釋放資源
}
}
接下篇《JavaSE 基礎學習之五 —— IO 操作》