前言:
寫了兩周的博客了,深的淺的都有,總有人問我寫博客有什么用?能出書嗎?寫的太淺有人看嗎?對找工作有什么幫助嗎?不想爭辯,也不愿把自己說的多么高大上,分享本身是一件簡單的小事,但是能給我?guī)砜鞓?,我享受別人看過我博客之后有種覺得有收獲那種感覺,就這么簡單。還有那些害怕寫博客被別人質(zhì)疑太淺的,其實大可不必,總有一些人掌握的知識沒有你多,也許你的一句話就解決了他們的疑問。言歸正傳,今天給大家?guī)鞪ava異常體系。
1、Java異常體系結(jié)構(gòu)圖

1.1、結(jié)構(gòu)角度分類
Error類:系統(tǒng)錯誤,我們最熟悉的OutOfMemeroyError、StackOverFlowError、NoClassDefoundError等都是該類的子孫,這些錯誤主要與機器有關(guān)。值得注意的是Error我們也可以通過try catch捕獲到,盡管捕獲到意義不大,比如OutOfMemeroyError我們捕獲到要進行處理是不可能的,因為系統(tǒng)已經(jīng)不工作了。
Exception:異常類,一個Exception發(fā)生了那么證明程序員寫的代碼出問題了,而不是機器的問題。
1.2、Java對異常處理要求分類
Checked Exception類:
A:體系圖中的紅色部分,編譯期間Java編譯器會強制要求程序員對該類異常進行處理,要么try catch、要么throw拋出去給調(diào)用層處理,否則無法執(zhí)行。Eclipse里面那些給我們提示的實際上都是這類異常。
B:為什么會存在這種異常呢?簡單來說就是對客觀上存在的潛在錯誤進行預(yù)處理,就像我們可能并不是每個人都會得天花,但是我們必須接種天花疫苗。Java號稱平臺無關(guān),程序員寫的代碼可能會出現(xiàn)在各種環(huán)境中,拿IOException來說,假如代碼運行的機器斷網(wǎng)了,那么網(wǎng)絡(luò)IO就有可能出現(xiàn)異常,如果不處理那么我們的程序就崩了,可檢查異常一般都是大概率出現(xiàn)的異常,因此需要提前處理。
Unchecked Exception類:
A:體系圖中藍色部分,主要包括Error和RuntimeException,編譯器不會檢查該類型異常,也檢查不到,比如我們最討厭的NullPointerException,編譯器不會強制程序員處理這種異常。當(dāng)然,程序員可以主動try catch捕獲這種異常然后進行處理,但是假如你寫了除0的代碼,與其花時間和代碼量去做捕獲處理,還不如從根本上杜絕這個問題,RuntimeException很大程度上能反應(yīng)一個程序員代碼的健壯性。
B:既然無法捕獲,這種異常有什么存在的必要呢?這就不得不說異常的另一個重要屬性,那就是傳遞信息的屬性,一個異常本身就是一種信息,它會告訴程序員哪一行代碼拋出了異常,調(diào)用鏈?zhǔn)鞘裁礃幼拥模惓J鞘裁丛驅(qū)е碌?,常見的異常如下?/p>
//這一行冒號左邊標(biāo)注了異常類型,冒號右邊標(biāo)注了異常原因
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1
//這一行是異常拋出位置,在代碼的第15行,c方法里
at com.huo.demos.test.Test.c(Test.java:15)
//以下三行是方法異常傳遞路徑,即方法調(diào)用路徑
at com.huo.demos.test.Test.b(Test.java:10)
at com.huo.demos.test.Test.a(Test.java:6)
at com.huo.demos.test.Test.main(Test.java:19)
2、自定義異常
2.1、什么時候需要自定義異常?
- 情況一:系統(tǒng)定義的異常信息和不夠充分。拿常見的NullPointerException來說,該提示只會給出哪里出現(xiàn)了空指針異常,而如果我們自定義類似的異常我們就可以給出是哪個變量因為哪種原因在哪里拋出了該異常。
- 情況二:團隊約定。比如團隊開發(fā)一個API,要統(tǒng)一對外的異常展示。
-
情況三:程序可能出現(xiàn)錯誤但無法預(yù)知誰會調(diào)用該程序。比如入?yún)⒁笫请娫捥柎a,調(diào)用方可能會輸入英文字符,這個時候我們有兩種選擇,一種是做判斷之后返回一個error code,此時的方法返回值就會被限制為int類型,而且需要給調(diào)用方出示一個error code含義表;另一種方法就是拋出異常,我不用考慮調(diào)用者是誰,也不用限定返回值,調(diào)用方只要調(diào)用了我的代碼編譯器就會提示他必須對這種可能的錯誤進行預(yù)處理。
image.png
2.2、如何自定義異常?
- A:繼承Exception:
Exception沒有強制要求子類實現(xiàn)其方法,但最好還是實現(xiàn)一下。當(dāng)然如果只為給一個簡單的提示或者根據(jù)異常名稱就可以快速知道發(fā)生了什么,也可以不用定義這些。
public class EException extends Exception {
public EException() {
super();
}
public EException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public EException(String message, Throwable cause) {
super(message, cause);
}
public EException(String message) {
super(message);
}
}
- B:繼承RuntimeException
public class RException extends RuntimeException {
public RException() {
super();
}
public RException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public RException(String message, Throwable cause) {
super(message, cause);
}
public RException(String message) {
super(message);
}
public RException(Throwable cause) {
super(cause);
}
}
-
C:兩者區(qū)別:
首先:繼承Exception的異常調(diào)用方必須拋出或者try catch處理,這是編譯器強制的,而繼承RuntimeException的異常不會這樣。
繼承Exception的異常
繼承RuntimeException的異常
然后:繼承Exception的異常應(yīng)該出現(xiàn)在這種情況里,即我的代碼極有可能出現(xiàn)這種異常,因為我無法考慮到所有環(huán)境,因此我拋出給調(diào)用方根據(jù)自己的環(huán)境進行對應(yīng)處理。繼承RuntimeException的異常應(yīng)該出現(xiàn)在此種環(huán)境里,即我的代碼出現(xiàn)這種異常的可能性不是特別大,但是還是有出現(xiàn)的可能,而在出現(xiàn)這種異常的時候我能夠給調(diào)用方以足夠的提示信息告知他發(fā)生了什么。
3、異常捕獲機制
3.1、catch捕獲范圍
從第一個catch開始到最后一個catch形成捕獲隊列,按照隊列的順序誰先捕獲到就歸誰處理。這個過程要考慮多態(tài)的問題,由于Exception是許多異常類的父類,因而如果處在隊列前面,后面的隊列的聲明就沒有意義了。
public static int bMethod() {
try {
return cMethod(true);
} catch (EException e) { //第一個捕獲
e.printStackTrace();
} catch (Exception e) { //第一個沒捕獲到,第二個來捕獲
e.printStackTrace();
} finally {
return 1;
}
}
3.2、finally的執(zhí)行問題
finally的中文意思是最終,也就是段try catch finally代碼的try catch該執(zhí)行的都執(zhí)行完之后最終要執(zhí)行finally里的代碼。簡單起見可以把try catch finally看成一個代碼塊。
為了更好的解釋finally運行機制,我們先了解一下虛擬機棧幀。

我們知道,每個線程都有一個自己的??臻g,實際上,當(dāng)方法執(zhí)行的時候JVM會把方法制作成棧幀壓入到操作棧中,每個方法都對應(yīng)著一個棧幀,當(dāng)前執(zhí)行的棧幀總是位于棧頂。
棧幀包含局部變量表、操作數(shù)棧、動態(tài)連接、方法返回地址。這里的方法返回地址實際上是在方法遇到ret指令之后返回值存放的位置的地址,也就是說返回值并沒有直接返回給方法的調(diào)用者,而是中間又多了一步將返回值存放到棧的某一個位置。在真正把返回值交給上層調(diào)用者之前,如果遇到break、continue、return、拋出異常等情況JVM會清除棧中的返回值
- 例子1,返回100
public static int method1() {
int k = 0;
try {
k = 1;
//返回的k=1首先存儲到棧中的返回值位置,如果順利完成就把該位置的值返回,如果遇到特殊指令如break、continue、return、拋異常就清除
return k;
} catch (Exception e) {
e.printStackTrace();
} finally {
//清除原來的返回值,把100放到返回值的位置上,如果順利結(jié)束就返回,否則清除
return 100;
}
}
- 例子2,返回100
public static int method2() {
int k = 1;
while (true) {
try {
return k;
} catch (Exception e) {
e.printStackTrace();
} finally {
//遇到break,返回值位置的值被清除
break;
}
}
k=100;
return k;
}
- 例子3,說明finally不是一定會執(zhí)行
public static int method1() {
int k = 1;
//此處拋異常或者try和catch里調(diào)用system.exit()方法finally就不會執(zhí)行。
Integer.parseInt(null);
try {
return k;
} catch (Exception e) {
e.printStackTrace();
} finally {
return k;
}
}
總結(jié)
其實日常開發(fā)我們遇到最多的就是NPE問題,NPE問題即是小問題又是破壞健壯性的大問題,我們應(yīng)該更好的防范一下。我刷leetcode和進行一些日常開發(fā)總結(jié)了一些經(jīng)驗,供大家參考下:
- 1、入?yún)⑹菍ο蟮臅r候應(yīng)該對入?yún)⑦M行判空操作
- 2、調(diào)用的其他人寫的方法返回的是對象的時候應(yīng)該對該對象進行判空操作
- 3、調(diào)用數(shù)據(jù)庫或者遠程調(diào)用的返回值應(yīng)該進行判空操作
- 4、保證調(diào)用者為已知對象。str1.equals(str2)方法,str1應(yīng)該是已知對象,str2可以為空;String.valueOf()和Integer/Long/Double.toString()返回值相同使用前者
- 5、自己寫的方法盡量不要返回空值


