什么是異常
從事Java
開發(fā)的小伙伴對于“異澈拦瑁”應(yīng)該不陌生香璃,因?yàn)槊刻於紩龅讲簧佼惓#虿东@舟误,或拋出葡秒。那究竟什么是異常?異常即非正常的嵌溢,不同于平常眯牧、一般化的情況
。在平時生活中赖草,醫(yī)生會說你身體的某個部位有異常学少,該異常會有什么什么的影響,是由某某原因引起的秧骑;我每天都準(zhǔn)時打卡版确,按時上下班扣囊,那么我本月的考勤是正常的,反之绒疗,但凡有遲到侵歇、曠工、早退的情況之一的吓蘑,我本月的考情就會有異常惕虑。
而在程序中,代碼在運(yùn)行中如果出現(xiàn)運(yùn)行錯誤磨镶,程序會終止運(yùn)行溃蔫,這時由于錯誤導(dǎo)致程序運(yùn)行終止的情況就是程序出現(xiàn)了異常。異常并不是指語法錯誤琳猫,因?yàn)槿绻Z法錯了伟叛,編譯就通不過,不會產(chǎn)生JVM能夠識別的字節(jié)碼文件脐嫂,是沒法運(yùn)行起來的统刮,所以只有運(yùn)行中的程序才會有異常一說。
Java 異常體系
異常處理是衡量一門語言是否成熟的標(biāo)準(zhǔn)之一雹锣,C
系列的語言諸如:Java
网沾、C++
癞蚕、C
等都支持異常處理蕊爵,有自己的一套異常處理機(jī)制。異常處理機(jī)制可以讓程序有更好的容錯性桦山,使代碼更加健壯攒射;但C
語言卻沒有異常處理機(jī)制,C
程序員一般都是利用方法的返回值來實(shí)現(xiàn)異常處理恒水,使用if + 條件
來判斷正常和異常情況会放,使用特定返回值來表示異常情況。
引入異常處理機(jī)制可以解決的問題有:
- 使用方法的返回值來表示異常情況有局限性钉凌,需要無窮列舉所有的異常情況咧最;
- 異常流程代碼和正常流程代碼混合一起,代碼往往很臃腫御雕,也很復(fù)雜性矢沿;代碼的可讀性和可維護(hù)性都不高;
- 隨著系統(tǒng)規(guī)模的不斷擴(kuò)大酸纲,代碼很難維護(hù)捣鲸,特別是系統(tǒng)可拓展性差;
通過一下這個案例來說明引入異常之前的處理方式:
// 汽車
class Car {
// 汽車是否正常運(yùn)行
public static final boolean IS_OK = true;
// 汽車運(yùn)行
public boolean run(int wheelNum) {
if (wheelNum == 4) {
return true;
}
return false;
}
}
// 上班族
class Officer {
private Car car;
public Officer(Car car) {
this.car = car;
}
// 開車去上班
public boolean goWorkByCar(int wheelNum) {
if (car == null) {
// 只能選擇其他出行方式了
goWorkByOther();
return false;
}
boolean result = car.run(wheelNum);
if (result) {
System.out.println("升職闽坡,加薪栽惶,迎娶白富美");
return true;
}
System.out.println("遲到愁溜,扣薪,領(lǐng)導(dǎo)天天噴");
return false;
}
// 其他方式上班
public void goWorkByOther() {
System.out.println("其他方式上班");
}
}
// 運(yùn)行demo
public class WorkDemo {
public static void main(String[] args) {
Car car = new Car();
Officer officer = new Officer(car);
officer.goWorkByCar(4);
}
}
上述案例中外厂,只是簡單粗暴的把汽車的狀態(tài)分為true
和false
兩種冕象,細(xì)化不夠,不能體現(xiàn)出汽車的詳細(xì)狀態(tài)酣衷;業(yè)務(wù)邏輯也很局限交惯,如果要拓展,就要花費(fèi)更大的成本穿仪。
針對于上述的問題席爽,Java
基于面向?qū)ο蟮乃枷胩岢隽私鉀Q方案:
- 把不同類型的異常情況使用不同的類來表示,不同的異常類有共同的父類啊片;
- 分離異常流程代碼和正確流程代碼只锻;
- 規(guī)范了異常處理機(jī)制,靈活處理異常紫谷,能處理就將其捕獲并處理齐饮,如果處理不了異常,就將其交給調(diào)用者來處理笤昨;
Java 異常體系:Java API
文檔中的詳細(xì)介紹如下
-
Error:表示錯誤祖驱,一般指
JVM
相關(guān)的不可修復(fù)的錯誤,如:系統(tǒng)崩潰瞒窒、內(nèi)存溢出捺僻、JVM
內(nèi)部錯誤等,由JVM
拋出崇裁,我們一般情況下不需要處理匕坯,幾乎其所有的子類都是以“Error”
作為類名后綴;比如:StackOverflowError拔稳,當(dāng)應(yīng)用程序遞歸太深而發(fā)生內(nèi)存溢出時葛峻,就會拋出該錯誤。 -
Exception:表示異常巴比,指程序中出現(xiàn)不正常的情況术奖,異常一般都是需要程序員來處理的(可以捕獲或者拋出);幾乎其所有的子類都是以
“Exception”
作為類名的后綴轻绞; -
Throwable:在
Java
體系中采记,Throwable類是所有錯誤和異常的父類;
當(dāng)出現(xiàn)了沒見過的異常時铲球,可以將異常類的類名拿到Java API
文檔中去查找挺庞,通過文章介紹即可獲得異常的詳細(xì)信息,以及其在Java
中的繼承稼病、實(shí)現(xiàn)體系选侨;常見的Exception
有:
-
NullPointerException:空指針異常掖鱼,一般當(dāng)對象為
null
的時候,對該對象做操作時會出現(xiàn)該異常援制; - ArrayIndexOutOfBoundsException:數(shù)組的索引越界戏挡,操作數(shù)組時使用的索引超出了數(shù)組的數(shù)據(jù)范圍會出現(xiàn);
- NumberFormatException:數(shù)字格式化異常晨仑,把非數(shù)字的數(shù)據(jù)類型轉(zhuǎn)換為數(shù)字類型時使用了非法的轉(zhuǎn)換對象褐墅;
Java 的異常詳解:
public class ExceptionDemo {
public static void main(String[] args) {
Integer.valueOf("laofu");
}
}
運(yùn)行結(jié)果如下:
如果出現(xiàn)異常,會立刻中斷運(yùn)行中的程序洪己,所以必須處理異常妥凳,而處理方式有兩種:
- throws:當(dāng)前方法不處理,而是聲明拋出答捕,由該方法的調(diào)用者來處理逝钥;
- try-catch:在當(dāng)前方法中使用try-catch的語句塊來處理異常;
捕獲異常 try-catch
出現(xiàn)異常之后拱镐,程序會中斷執(zhí)行艘款,所以異常必須處理;處理的方式有兩種:捕獲和拋出沃琅,兩種方式二選一即可哗咆。我們先來運(yùn)行一個案例來證明:
public static void main(String[] args) {
System.out.println("老夫開始啦");
int result = 10 / 2;
System.out.println("10 / 2 = " + result);
System.out.println("老夫去也");
}
運(yùn)行結(jié)果如下:
老夫開始啦
10 / 2 = 5
老夫去也
通過查看運(yùn)行結(jié)果,是我們期望的運(yùn)行結(jié)果益眉,代碼運(yùn)行成功晌柬;那么接下來我們對上述案例稍作修改,再來看其運(yùn)行結(jié)果如何:
public static void main(String[] args) {
System.out.println("老夫開始啦");
int result = 10 / 0;
System.out.println("10 / 2 = " + result);
System.out.println("老夫去也");
}
運(yùn)行結(jié)果如下:
老夫開始啦
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Main.main(Main.java:5)
通過查看運(yùn)行結(jié)果呜叫,運(yùn)行結(jié)果并不是我們想要的空繁,代碼中出現(xiàn)了異常殿衰,代碼被中斷運(yùn)行朱庆。對比兩次的運(yùn)行結(jié)果,我們可以得出結(jié)論:出現(xiàn)異常之后闷祥,程序會中斷執(zhí)行娱颊,異常必須處理。
try-catch 異常捕獲:使用try-catch捕獲單個異常凯砍,語法如下:
try{
編寫可能會出現(xiàn)異常的代碼
} catch(異常類型 e) {
處理異常的代碼
//記錄日志/打印異常信息/繼續(xù)拋出異常
}
注意:try和catch都不能單獨(dú)使用箱硕,必須連用
所以可以對上述出現(xiàn)異常的代碼使用try-catch
來處理:
public class ExceptionDemo {
public static void main(String[] args) {
System.out.println("老夫開始啦");
try {
int result = 10 / 0;
System.out.println("10 / 2 = " + result);
} catch (ArithmeticException e) {
System.out.println("異常信息:" + e.getMessage());
System.out.println("異常對象:" + e.toString());
System.out.println("異常棧追蹤:");
e.printStackTrace();
}
System.out.println("老夫去也");
}
}
運(yùn)行結(jié)果如下:
老夫開始啦異常信息:/ by zero
異常對象:java.lang.ArithmeticException: / by zero
異常棧追蹤:老夫去也java.lang.ArithmeticException: / by zero
at Main.main(Main.java:6)
通過查看運(yùn)行結(jié)果,不難發(fā)現(xiàn)悟衩,使用try-catch
之后剧罩,程序遇到異常時不再中斷執(zhí)行,而是跳過異常代碼及其之后的在try-catch
中的剩余代碼語句座泳,來到catch
代碼塊中執(zhí)行我們定義的異常處理代碼惠昔;
在上述案例中是打印出了異常信息幕与,異常對象信息,異常棧追蹤镇防;其中的3個方法都是Throwable類的方法:
-
String getMessage()
:獲取異常的詳細(xì)描述信息啦鸣; -
String toString()
:獲取異常的類型、異常描述信息来氧; -
void printStackTrace()
:打印異常的跟蹤棧信息并輸出到控制臺诫给,但不能在System.out.println()
中使用該方法;其中包含了異常的類型啦扬、異常的原因中狂、異常出現(xiàn)的位置;在開發(fā)和調(diào)試階段扑毡,該方法都很有用吃型,方便調(diào)試和修改;
使用try-catch捕獲多個異常:語法如下:
try{
編寫可能會出現(xiàn)異常的代碼
} catch (異常類型A e){ // 當(dāng)try中出現(xiàn)A類型異常,就用該catch來捕獲.
處理異常的代碼
//記錄日志/打印異常信息/繼續(xù)拋出異常
} catch (異常類型B e){ // 當(dāng)try中出現(xiàn)B類型異常,就用該catch來捕獲.
處理異常的代碼
//記錄日志/打印異常信息/繼續(xù)拋出異常
}
注意
- 一個
catch
語句僚楞,只能捕獲一種類型的異常勤晚,如果需要捕獲多種異常類型,就得使用多個catch
語句泉褐; -
try-catch
中的代碼在只會出現(xiàn)一種類型的異常赐写,只能一個catch
捕獲,不可能同時匹配多個catch
膜赃; - 在有多個
catch
語句的代碼中出現(xiàn)異常時挺邀,會從上到下依次匹配catch
語句,所以多個catch
語句應(yīng)該按照從子類到父類的順序依次定義跳座; - 一旦匹配上其中一個
catch
之后端铛,便不會匹配剩余的catch
,而是會跳出try-catch
疲眷,執(zhí)行之后的代碼禾蚕;
捕獲多個異常的案例:
運(yùn)行結(jié)果如下:
老夫開始啦
NumberFormatException
老夫去也
java.lang.NumberFormatException: For input string: "laofu"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Integer.parseInt(Integer.java:580)
at java.lang.Integer.parseInt(Integer.java:615)
at Main.main(Main.java:7)
拋出異常
拋出異常有兩種方式:
- throw:用于方法內(nèi)部,用于給調(diào)用者返回一個異常對象狂丝,和return一樣會結(jié)束當(dāng)前方法换淆;
-
throws:運(yùn)用于方法聲明之上,定義于方法參數(shù)之后几颜,表示當(dāng)前方法不處理異常倍试,而是提醒該方法的調(diào)用者來處理拋出的異常(一個或者多個異常);如:
private static int divide(int num1, int num2) throws Exception {}
throw語句:運(yùn)用于方法內(nèi)部蛋哭,拋出一個具體的異常對象县习,中止方法的執(zhí)行,其語法格式如下:
throw new 異常類("異常信息");
一般的,當(dāng)一個方法出現(xiàn)異常的情況躁愿,我們不知道該方法應(yīng)該返回什么時哈蝇,此時就可以返回一個錯誤,在catch
語句塊中使用throw
繼續(xù)向上拋出異常攘已。return
是返回一個值炮赦,throw
是返回一個錯誤,返回給該方法的調(diào)用者样勃。比如:String
類的charAt
方法就是一個很好的例子:
throws:
如果每一個方法都放棄處理異常都直接通過throws
聲明拋出吠勘,最后異常會拋到main
方法,如果此時main
方法還不處理峡眶,會繼續(xù)拋出給JVM
剧防,JVM
底層的處理機(jī)制就是打印異常的跟蹤棧信息;runtime
異常辫樱,默認(rèn)就是這種處理方式峭拘。
方法重寫(Override)中的throws:
一同:方法的簽名必須相同。
兩惺ㄊ睢:
- 子類方法返回類型和父類方法返回類型相同鸡挠,或是其子類;
- 子類方法不能聲明拋出新的異常搬男;
一大:子類方法的訪問權(quán)限必須大于等于父類方法的訪問權(quán)限拣展。
異常分類
異常(Exception
)根據(jù)其在編譯時期還是運(yùn)行時期去檢查異常可分為:checked異常和runtime異常缔逛,
-
runtime異常:又稱運(yùn)行時期異常备埃,此類型的異常在運(yùn)行時期檢查;在編譯時期褐奴,運(yùn)行異常并不會檢測按脚,就不會出現(xiàn),只有在運(yùn)行到相關(guān)代碼時才會出現(xiàn)敦冬;
RuntimeException
自身及其子類異常都屬于runtime異常辅搬; -
checked異常:又稱編譯時期異常,此類型的異常在編譯時期就會檢查匪补,而且是必須處理的伞辛,如果沒有處理烂翰,就會導(dǎo)致編譯失敽蝗薄;除了
runtime異常
之外的其他異常(包括Exception
自身)都屬于checked異常
甘耿;
自定義異常
Java
中有著不同的定義好的異常類踊兜,分別表示著某一種具體的異常情況,在開發(fā)中總是有些異常情況是Java SE
庫中沒有定義好的佳恬,此時就可以根據(jù)自己業(yè)務(wù)的異常情況來定義異常類捏境;我們把這樣的異常類稱為自定義異常類于游。
自定義異常類的方式:
受檢查的異常:自定義一個受檢查的異常類需要繼承于
java.lang.Exception
;運(yùn)行時異常:自定義一個運(yùn)行時期檢查的異常類垫言,需要繼承于
java.lang.RuntimeException
贰剥;
一般在開發(fā)中,自定義的異常都是運(yùn)行時異常筷频。
解決開車上班的案例
異常轉(zhuǎn)譯和異常鏈
異常轉(zhuǎn)譯:位于最外層的業(yè)務(wù)系統(tǒng)不需要關(guān)心底層的異常細(xì)節(jié)蚌成,我們通過捕獲原始的異常,將其轉(zhuǎn)換為一個新的不同類型的異常凛捏,然后再向上拋出担忧;這個過程稱為異常轉(zhuǎn)譯。
在上述例子中:我的車壞了坯癣,在catch
中重新拋出一個新的異常(OfficerException
)給我的調(diào)用者(老板)瓶盛,不能把車的異常信息拋給老板看,因?yàn)槔习宀魂P(guān)心這些細(xì)節(jié)示罗,關(guān)心的我是否遲到惩猫。
異常鏈:把原始異常包裝為新的異常類,形成多個異常的有序排列蚜点;異常鏈由于更加清楚帆锋、準(zhǔn)確的定位異常出現(xiàn)的位置;在下述案例中禽额,異常一層層拋出锯厢,直至異常被處理,在這個過程中脯倒,異常鏈就產(chǎn)生了:
Java7的異常新特性
1.增強(qiáng)的throw : 對比Java 6
和 Java 7
中對于拋出異常的改進(jìn)來體現(xiàn)
2.多異常捕獲:重寫捕獲多個異常案例來體現(xiàn)
3.自動資源關(guān)閉:資源類必須直接或者間接實(shí)現(xiàn)java.lang.AutoCloseable
接口
finally代碼塊
finally
語句塊表示無論如何(也包括發(fā)生異常時)都會最終執(zhí)行的代碼塊实辑,比如:當(dāng)我們在try
語句塊中打開了一些物理資源(磁盤文件/網(wǎng)絡(luò)連接/數(shù)據(jù)庫連接等),在使用完之后藻丢,都得最終關(guān)閉打開的資源剪撬。
finally的兩種用法:
1. try...finally: 此時沒有catch
來捕獲異常,因?yàn)榇藭r根據(jù)應(yīng)用場景會拋出異常悠反,我們程序員自己不處理残黑;
2. try...catch....finally:程序員自己需要處理異常界拦,最終得手動關(guān)閉資源俗冻。
需要注意的是:finally不能單獨(dú)使用。
finally不執(zhí)行的情況:
當(dāng)只有在try
或者catch
中調(diào)用退出JVM的相關(guān)方法僚饭,此時finally
才不會執(zhí)行茵臭,否則finally
修飾的代碼塊永遠(yuǎn)會執(zhí)行疫诽。比如:System.exit(0); // 退出JVM
finally 和 return
如果finally和return語句同時存在,永遠(yuǎn)返回finally中的結(jié)果,但是應(yīng)該避免這種情況的出現(xiàn)奇徒。詳情看如下的案例:
如果finally
和return
語句同時存在,永遠(yuǎn)返回finally
中的結(jié)果
還有另一種情況也很有趣摩钙,一起來看看:
為什么會出現(xiàn)這種情況呢罢低?首先finally
肯定是會被執(zhí)行的,所以a++
之后a的值變成了14
胖笛,但是finally
中沒有返回值奕短,值為14
的變量a
并沒有被返回;然后接著執(zhí)行return a匀钧;
這里的a
的值在方法執(zhí)行之初就已經(jīng)確定了翎碑,故返回的值是13
。
異常處理原則
處理異常的原則:
- 異常只能用于非正常情況之斯,
try-catch
的存在也會影響性能日杈,盡量縮小try-catch
的代碼范圍; - 需要為異常提供說明文檔佑刷,可以參考
Java doc
莉擒,如果自定義了異常或某一個方法拋出了異常瘫絮,應(yīng)該在文檔注釋中詳細(xì)說明涨冀; - 盡可能避免異常的出現(xiàn),如
NullPointerException
等麦萤; - 異常的粒度很重要鹿鳖,應(yīng)該為一個基本操作定義一個
try-catch
塊,切忌將幾百行代碼放到一個try-catch
塊中壮莹; - 不建議在循環(huán)中進(jìn)行異常處理翅帜,應(yīng)該在循環(huán)外對異常進(jìn)行捕獲處理(在循環(huán)之外使用
try-catch
); - 自定義異常盡量使用
RuntimeException
類型的命满,并且要盡量避開已存在的異常涝滴;
小結(jié)
1. 五個關(guān)鍵字:try、catch胶台、finally歼疮、throw、throws
诈唬;
2. 異常體系的兩個繼承結(jié)構(gòu):
-
Throwable
類有兩個子類:Error
和Exception
韩脏。 -
Exception
類有一個特殊的子類:RuntimeException
。
RuntimeException
類及其子類稱之為runtime異常
讯榕。
Exception
類和子類中除了RuntimeException
體系的其他類稱之為checked異常
骤素。
3. 自定義異常類
4. Error
和Exception
的區(qū)別和關(guān)系
5. checked
異常和`runtime異常的區(qū)別
6. finally
關(guān)鍵字及其相關(guān)知識
7. finally
和return
的執(zhí)行順序
8. throw
和throws
的區(qū)別
完結(jié)匙睹。老夫雖然不正經(jīng)愚屁,但老夫一身的才華济竹!