導讀
本文適合Java入門,不太適合Java中高階軟體工程師。本文以《Java程式設計基礎篇》第10版為藍本,採用不斷提出問題,然後解答問題的方式來講述。本篇文章只是這個系列中的一篇,如果你喜歡這種講解方式,或者覺得從中能學到知識,可以關注我,以便查閱本系列其他文章。
讓我們開始愉快地學習Java語言吧!
1基本概念
執行時錯誤:程式執行過程中,JVM檢測到一個不可能完成的操作而丟擲錯誤。
異常:它是異常類的例項,表示阻止程式正常執行的錯誤或情況。
異常處理器:處理異常的程式碼。
捕獲一個異常:尋找處理器的過程。
異常在什麼地方丟擲呢?
異常可以在方法中丟擲。
異常應該被誰捕獲呢?
異常被方法的呼叫者捕獲。
2異常型別有哪些?
看下面這個UML,
所有型別的異常都擴充套件自Throwable,它有兩個子類Error和Exception。Error和Exception也有許多子類,例如:
Error也有一些子類,例如:
異常型別分為必檢異常(checked exception)和免檢異常(unchecked exception)
必檢異常指編譯器強制程式設計師檢測這種異常,並使用try-catch處理或者在方法頭宣告必檢異常。
免檢異常不強制程式設計師檢測和處理這種異常。免檢異常包括:RuntimeException、 Error及其子類。
除RuntimeException、 Error及其子類外都是必檢異常。
3如何宣告異常?
在方法頭使用throws關鍵字宣告異常,多個異常用逗號分隔。
例如,類A的方法f宣告丟擲IOException
4如何丟擲異常呢?
使用throw語句丟擲異常。
語法為 :
throw 異常例項;
5如何捕獲異常呢?
將方法用try-catch塊包裹起來就可以在catch塊中捕獲異常。
語法為:
try{
//方法
}catch{
//處理異常
}
或者
try{
//方法
}catch(異常型別 ex){
//處理異常
}
或者,一次捕獲多個異常,這種方式要注意子類應該排在父類前面。
try {
//方法
}
catch (Exceptionl exVarl){
//處理異常
}catch (Exception2 exVar2){
//處理異常
}
。。
catch (ExceptionN exVarN){
//處理異常
}
如果try塊中不止一條語句,丟擲異常的語句後面還有一條或多條,那麼後面的語句會執行嗎?
不會,將跳過try塊後面的語句,直接跳到catch塊。
如果在執行try塊的時候沒有異常丟擲,那麼catch塊會執行嗎?
當然不會,將跳過catch塊執行下面的語句。
捕獲多個異常時,是如何匹配異常引數的呢?
依次查詢catch塊的引數,直到找到相匹配的異常型別後,就進入那個catch塊。這裡“相匹配的異常”有兩層含義,一是try塊中語句丟擲的異常和catch塊的引數型別相同,或者try塊中語句丟擲的異常是catch塊的引數型別的子型別。
如果多個方法形成了呼叫鏈,如何找到異常處理器呢?
從當前的方法開始,沿著方法呼叫鏈,按照異常的反向傳播方向找到異常處理器。
我們驗證一下:
在類A中定義了f1,f2,f3這三個方法,f1呼叫了f2,f2呼叫了f3,只有f3會丟擲異常並處理它。
在main方法中呼叫f1,觀察發現f3處理了異常,並將異常的追蹤資訊打印出來。
若在f2中新增對異常的處理,而f3不處理異常,然後觀察結果,先將A按如下修改:
呼叫f1,觀察執行結果,透過列印的資訊,依然可以追蹤到哪一行真正丟擲異常:
此外,一次捕獲多個異常還有一種簡化的寫法:
catch (Exceptionl|Exception2|… |Exceptionk ex){
//處理異常
}
依然要注意catch的引數子類在前,父類在後。
6從異常中獲得資訊
獲取異常資訊常用的幾個方法有:
printStackTrace():顯示棧跟蹤
getStackTrace():獲得棧跟蹤
getMessage():異常資訊
toString():異常物件和資訊
讓我們把上例中A類的方法修改一下,列印上述方法返回的資訊,讓我們看看具體的資訊。
7finally子句
當異常從一個方法丟擲時,如果還有一些收尾工作沒有完成,那麼也會被異常中斷,例如,操作一個網路資源之前需要建立一個連線到網路資源的連線物件,由於每個連線都會消耗掉一部分資源,所有操作完成,獲得結果後,要關閉連線,釋放連線所佔用的資源,如果呼叫操作網路資源的方法丟擲異常,那麼後續的操作將無法進行,導致無法釋放連線所佔用的資源。
如何做到即使丟擲異常也可以不中斷釋放資源呢?
使用finally子句可以解決問題。
語法結構為:
try {
//可能會丟擲異常的語句
}catch(異常型別 ex){
//處理異常
} finally {
//收尾工作
}
不論何種情況,finally子句都會執行,即使在finally子句之前有return語句。但要特別留意的是,finally子句中的程式碼也有可能丟擲異常。
8何時使用異常呢?
不能亂用異常,也許對不可能丟擲異常的語句使用try-catch也能使程式正常執行,但是那樣會使程式碼糟糕難懂。
對可能丟擲異常的地方,並且要對這種異常進行處理的時候,我們才使用try-catch。
同時,注意不要把異常處理算作業務邏輯的一部分。
9重新丟擲異常
當對捕獲的異常進行處理後,還想將異常向上傳遞,那麼可以在catch塊中丟擲異常,可以對已捕獲的異常進行包裝後,在丟擲。
10 鏈式異常
和其他異常一起丟擲一個異常,就構成了鏈式異常。
鏈式異常有啥好處呢?
我們看到,新定義的異常資訊連同f3中的異常資訊一起輸出。並且指明引起異常的原因。
11自定義異常類
如果JDK API提供的異常愉快地學Java語言:第十章:抽象類和介面型別不能滿足需求,那麼可以派生java。lang。Exception類來定義一個異常。
當我們擴充套件Exception時,編譯器會提示我們新增一個序列化ID,那麼為啥需要這個呢?
簡單講就是,如果使用者沒有自己宣告一個serialVersionUID,介面會預設生成一個serialVersionUID,因為預設的serialVersinUID對於class的細節非常敏感,反序列化時可能會導致InvalidClassException這個異常。所以還是新增的好。
另外,注意一般不要擴充套件免檢異常,通常我們自定義異常就是為了檢測它並捕獲它。
或許你還想看看往期文章:
愉快地學Java語言:第十章:抽象類和介面
愉快地學Java語言:第九章 面向物件基本特徵 第1講
愉快地學Java語言:第九章 面向物件基本特徵 第2講
愉快地學Java語言:第九章 面向物件基本特徵 第3講