接上篇《JavaSE 基礎(chǔ)學(xué)習(xí)之三 —— Java 的繼承與接口》
四. Java 核心專題 —— 異常處理
1. 兩種類型的異常
- 運行時異常 (RuntimeException):不處理也能通過編譯顿痪,jvm 會幫助處理,也可以自行處理果复;
- 其他異常:對于其他異常屡萤,如果不處理程序就不能通過編譯抵赢,必須自己處理薇溃;
注:
所有的異常產(chǎn)生之后厌均,都是一個類的實例對象刨裆,而且這些異常全部繼承于 Exception维贺;java 中對所有的異常都進行了細(xì)致分類它掂,每種異常都有一個具體的類。
2. 異常處理的關(guān)鍵字
(1) try... catch...
- try 后面可以有多個 catch溯泣;
- 所有的異常都是 Exception 的子類虐秋;
- 多個 catch 的時候,父類的異常一定要寫在子類的后面垃沦;
- Exception 可以認(rèn)為是萬能異常客给;
- 常用 Exception 的方法:
- getMessage(): 返回此 throwable 的詳細(xì)消息字符串;
- toString():返回此 throwable 的簡短描述肢簿。
- printStackTrace():打印異常的堆棧靶剑,將此 throwable 及其追蹤輸出至標(biāo)準(zhǔn)錯誤流;
(2) finally
finally 關(guān)鍵字后無論有沒有異常池充,最終都要執(zhí)行的操作桩引;
例:
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("數(shù)組下標(biāo)越界");
} catch (ArithmeticException e) {
System.out.println("除數(shù)不能為 0");
} catch (Exception e) {
System.out.println(e.getMessage());
System.out.println(e);
System.out.println("其他異常");
} finally {
System.out.println("無論有沒有異常,最終都要執(zhí)行的操作");
}
}
}
(3) throw
throw 關(guān)鍵字表示在某種情況下纵菌,我們自己手動拋出的異常實例阐污;
例:定義一個 Person 類,對其年齡進行設(shè)置咱圆,如果年齡超過 120笛辟,則認(rèn)為出現(xiàn)錯誤,手動拋出異常序苏。
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 關(guān)鍵字在一個方法的方法簽名后面使用忱详,用處在于通過編譯围来,告訴使用者一旦調(diào)用了當(dāng)前方法,可能出現(xiàn)處理哪些異常匈睁,這樣可以令使用者盡量的回避異常监透;當(dāng)你的方法里拋出了 checked 異常,如你不 catch航唆,代表你當(dāng)時不處理(不想處理或沒條件處理)胀蛮,但你必須得通過"throws那個異常"告訴系統(tǒng)說,這兒有個問題糯钙,我現(xiàn)在不處理粪狼,將來一定別人要處理退腥,否則執(zhí)行到它,系統(tǒng)會"不優(yōu)雅"的崩潰再榄。
舉個例子狡刘,工兵張三發(fā)現(xiàn)了地雷,假如他處理完就完事兒了困鸥。但是他發(fā)現(xiàn)了地雷嗅蔬,自己卻沒帶齊工具,沒法處理窝革,他必須做個標(biāo)記购城,說這兒有一個地雷,別的工兵將來一定要處理虐译,否則將來有人踩上去會爆炸。
注意:throws只是標(biāo)記吴趴,并沒處理漆诽,程序執(zhí)行到那里,系統(tǒng)還是會崩潰锣枝!
例:
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. 自定義異常
項目中撇叁,我們可以通過創(chuàng)建一個類繼承 jdk 提供的異常類 (RunTimeException 或 Exception)供鸠,自定義的實現(xiàn)一個異常類。
自定義異常的創(chuàng)建有利有弊:優(yōu)點如下:
- 工作過程中陨闹,項目是分模塊或者分功能開發(fā)的楞捂,基本不會你一個人開發(fā)一整個項目,使用自定義異常類就統(tǒng)一了對外異常展示的方式趋厉;
- 有時候我們遇到某些校驗或者問題時寨闹,需要直接結(jié)束掉當(dāng)前的請求,這時便可以通過拋出自定義異常來結(jié)束君账。例如繁堡,如果你項目中使用了SpringMVC比較新的版本的話有控制器增強,可以通過@ControllerAdvice注解寫一個控制器增強類來攔截自定義的異常并響應(yīng)給前端相應(yīng)的信息乡数;
- 系統(tǒng)中有些錯誤是符合Java語法的椭蹄,但不符合我們項目的業(yè)務(wù)邏輯砰粹,使用自定義異掣馄可以在我們項目中某些特殊的業(yè)務(wù)邏輯時拋出異常。例如對于性別形庭,"中性".equals(sex)劫侧,性別等于中性時我們要拋出異常埋酬,而Java是不會有這種異常的哨啃;
- 使用自定義異常繼承相關(guān)的異常來拋出處理后的異常信息可以隱藏底層的異常,這樣更安全写妥,異常信息也更加直觀拳球。自定義異常可以拋出我們自己想要拋出的信息珍特,可以通過拋出的信息區(qū)分異常發(fā)生的位置祝峻,根據(jù)異常名我們就可以知道哪里有異常,根據(jù)異常提示信息進行程序修改扎筒。例如莱找,空指針異常NullPointException,我們可以拋出信息為“xxx為空”定位異常位置嗜桌,而不用輸出堆棧信息奥溺。
自定義異常的缺點主要在于,發(fā)現(xiàn)異常骨宠、拋出異常以及處理異常的工作必須靠編程人員在代碼中利用異常處理機制自己完成浮定。這樣就相應(yīng)的增加了一些開發(fā)成本和工作量,所以項目沒必要的話层亿,也不一定非得要用上自定義異常桦卒,要能夠自己去權(quán)衡。
關(guān)于自定義異常匿又,可以參考《Java異常之自定義異撤皆郑》的博客。
4. 關(guān)于異常的編程建議
建議:
- 自己拋出的異常必須要填寫詳細(xì)的描述信息碌更,便于問題定位裕偿;
- 例如:throw new IOException("Writing data error! Data: " + data.toString());
- 在程序中,選擇使用異常處理還是錯誤返回碼處理针贬,應(yīng)該根據(jù)是否有利于程序結(jié)構(gòu)來確定击费,且不能將異常和錯誤碼混合使用。通常情況下推薦異常處理桦他;
- 記錄異常時蔫巩,不要只保存 exception.getMessage(),一般可以通過日志工具記錄完整的異常堆棧信息快压;
- 一個方法不應(yīng)該拋出太多類型的異常圆仔。如果確實有很多異常類型,首先應(yīng)該考慮用異常來進行區(qū)別蔫劣。通常來說 throws / exception 子句標(biāo)明的異常最多不要超過三個坪郭;
- 異常捕獲盡量不要直接捕獲 catch(Exception ex),應(yīng)該把異常細(xì)分處理脉幢,設(shè)計更加合理的異常處理分支歪沃;
- public 類型的底層方法需要對輸入?yún)?shù)進行判斷嗦锐,如果參數(shù)不合法,應(yīng)該主動拋出 RuntimeException沪曙;
強烈建議:
- 在調(diào)用的最高層奕污,必須處理所有的異常;
- 如果捕獲了異常液走,然后拋出新的異常碳默,則必須將原異常的信息全部包含在新的異常中;
- 系統(tǒng)非正常運行產(chǎn)生的異常捕獲之后缘眶,如果不對該異常進行處理嘱根,必須記錄日志;
- 對于多個異常的額情況巷懈,必須對應(yīng)多個 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 基礎(chǔ)學(xué)習(xí)之五 —— IO 操作》