Java學習筆記——異常(Exception)
異常的分類
Throwable類
Throwable
:所有異常都是由Throwable
繼承而來的,可以通過繼承Throwable
來實現新的異常,但是一般不推薦這樣做,下一層分為了兩個分支:Error
和Exception
Error類
Error類
來用描述java運行時系統內部引起的錯誤和資源消耗錯誤,因為是java內部的錯誤,因此編寫的應用程序無能為力。
Exception類
Exception
:Exception類
又可以分為IOException
和RuntimeException
,一般程序應該通過檢測的方式盡量避免RuntimeException
(也就是說出現這種異常就是編寫者自己的問題,編寫者無需要捕獲這一類異常,相反,應該使用代碼if(obj == null)
來檢測避免出現NullPointException
)
RuntimeException類
RuntimeException
:一般包括:
- 錯誤的(類型)強制類型轉換:
ClassCastException
- 數組訪問越界:
ArrayIndexOutOfBoundsException
- 訪問null指針:
NullPointerException
IOException類
常見的IOException
類一般包括:‘
文件末尾繼續讀取數據:EOFException
試圖打開一個不存在的文件:FileNotFoundException
根據給定的字符串查找class對象,但是該類不存在:ClassNotFoundException
...
官方分類
Java一般把派生於Error
類和RuntimeException
類的異常稱之為非檢查型異常,而IOException
稱之為檢查型異常。
然而實際中,我們能夠進行處理的只有檢查型異常,因為檢查型異常在我們個人的控制之內,非檢查型異常對於任何的代碼都有可能拋出,出現這些異常的時候我們沒法控制。
舉個例子:任何獲取對象引用的方法,都有可能獲取對象引用失敗而返回一個NullPointerException
,這異常的出現是沒法控制的,除非你給每一個GetXX()
方法都加上異常處理,但是這並不現實,因此沒必要處理非檢查型異常。
拋出異常
聲明檢查型異常
如何聲明檢查型異常
如下:
public FileInputStream(String name) throws FileNotFoundException
若要聲明多個檢查型異常,則需要用逗號分割
public Image loadImage(String name) throws FileNotFoundException, EOFException
注意這里特別強調聲明檢查型異常,對於拋出非檢查型異常一般是不需要聲明的
什么時候需要聲明
兩種情況:
- 調用一個拋出檢查型異常的方法的時候,傳遞異常的拋出,例如調用
loadImage
方法 - 方法本身需要拋出檢查型異常
// situation 1
public Image loadNewImage(String name) throws FileNotFoundException, EOFException{
loadImage(name);
}
// situation 2
public Image importFile(String name) throws FileNotFoundException{
file f = readfile(name);
if(f == null){
throw new FileNotFoundException(); // 拋出異常
}
}
拋出異常
拋出異常的方法:
// method 1:
throw new EOFException(); // 拋出一個EOFException
// method 2:
var a = new EOFException();
throw a; // 拋出一個EOFException
一般怎樣決定拋出異常呢?通常考慮的情況是這樣:
- 找到一個合適的異常類
- 創建這一個異常類的對象
- 將對象拋出
一旦拋出異常之后,這個方法不會返回到調用者,也就是說無需要另外建議一個返回語句(return)
捕獲異常
捕獲異常
拋出檢查型異常之后,如果沒有對他進行捕獲,當前執行的線程都會終止,那么如何捕獲異常?
try{
// 先執行try里面的語句
// 一旦try里面的有一條語句拋出ExceptionTypeX類型異常,則進入相應的catch語句,終止之后的try代碼塊的執行
}catch(ExceptionType1 e){
// 在這里處理異常
}catch(ExceptionType2 e){
// 可以捕獲多個異常
}catch(ExceptionType3 | ExceptionType4 e){
// 如果拋出的兩個異常是不同的,但是他們的處理方法都一樣的話,還可以這樣捕獲
// 注意這種方式捕獲異常時,變量e被隱式聲明為final,因此不能改變e的值
}
}finally{
// 無論是否發生異常,最后都會運行此處的代碼,通常用於釋放資源
// finally代碼塊可以省略
// 注意不要把控制流的語句放在finally(return,throw,break,continue),因為會發生意想不到的錯誤
// 同時也不應該過分依賴finally,一般的設計原則是將finally應用在關閉資源或者釋放資源,如關閉IO流等
}
捕獲異常還是傳遞異常
我們可以通過捕獲異常來處理方法拋出的異常,但是並非每一個異常我們都知道怎么去處理,那要如何解決?
我們知道一個方法調用方法A,方法A會調用另外一個可能拋出異常的方法B時,我們可以通過在方法A中聲明檢查型異常的方式,來將方法B中的異常傳遞給調用了方法A的那一個方法(說得更加直白一點就是A方法將B方法拋給A的異常再次拋出給A的調用者),讓它來解決這一個異常,因此對於上面的問題,我們就可以采用這種方式來解決,類似於下面的解決方式。
public A() throws Exception {
B();
}
public B() throws Exception {
... // 處理代碼
if(...){
throw new Exception();
}
}
public fun(){
try{
A();
}catch(Exception e){
// do something
}
}
實際上對於應該捕獲異常還是傳遞異常這一個問題,最好的答案是除非你知道怎么解決,否則就應該什么都不做,將異常傳遞給最終的調用者,讓最終調用者來處理這個問題是最好不過的。
不過這種解決方案有一個例外:這個例外體現在繼承特性上:
如果一個子類覆蓋了超類的一個方法,那么子類要么不拋出異常,要么拋出的異常必須是超類異常的子類(也就是說子類需要拋出更加具體的異常),同樣,如果覆蓋的超類方法沒有拋出異常,那么子類的覆蓋方法也不能拋出異常。
因此,如果覆蓋的超類方法沒有拋出異常,而子類的覆蓋方法又調用了其他可能拋出異常的方法,這種時候就必須在覆蓋方法中捕獲所有的異常。
自定義異常類
通常我們需要滿足我們個人的一個程序需要的時候就需要自定義異常類,異常類的定義可以通過派生任何Exception類或者它的子類如IOException類來完成
public FileFormatException extends IOException
{
public FileFormatException(){}
public FileFormatException(String gride){
super(gride);
}
}
// 這樣就可以完成自己的異常的定義了
自定義的異常類若是IOException
(檢查型異常),則最后的方法調用者必須捕獲異常,編譯器一般會給出下划線來提示調用者需要捕獲該異常,而自定義的異常類若是RuntimeException
(非檢查型異常),則可以無需捕獲,編譯器也不會提示調用者,這也印證了開頭所說的那句話:你應該盡可能地避免非檢查型異常!
異常鏈與異常再次包裝
異常嵌套
先看下面的代碼:
InputStream i = ...;
try{
... // code 1
try{
// code 2
}catch(Exception e){
}finally{
i.close();
}
}catch(IOException e){
}
若finally中發生異常,則交由外層捕獲處理,若code 2位置發生異常,交由內層處理
再次拋出異常
很多時候我們不知道需要具體拋出一個什么異常,或者拋出去的異常可能帶有選擇性等情況,這時候就需要再次拋出新的子類異常,類似於下面的情況
try{
}catch(IOException e){
throw new FileNotFoundException();
}
包裝技術
對於再次拋出異常,如何防止原父類異常中的信息丟失?
try{
}catch(IOException e){
var a = new FileNotFoundException();
a.initCause(e);
throw a;
}
捕獲異常時,使用
Throwable original = a.getcause();
就可以獲取高層原始異常信息了。
包裝技術非常有用,可以將多種異常包裝成一類異常拋出,同時保留高層原始異常的信息。
try-with-resource語句
該語句用於簡化try-catch-finally
語句中的釋放工作
要使用try-with-resource
語句,需要res
實現AutoCloseable
接口,該接口只有一個方法
void close() throws Exception
try(Resource res = ...){
// work
}
使用try-with-resource
,代碼段在運行結束之后,無論是否有異常拋出,都會調用res
中實現的close()
。
一般情況下,只要需要關閉資源,就要盡可能使用try-with-resource
異常類相關方法
java.lang.Throwable
Throwable() // 構造一個新的Throwable對象,但沒有詳細的描述信息
Throwable(String message) // 構造一個新的Throwable對象,帶有詳細的描述信息
String getMessage() // 獲取Throwable對象的詳細描述信息
// 通常用於包裝的構造方法
Throwable(Throwable cause) // 用給定的cause(原因)構造來構造Throwable對象
Throwable(String message, Throwable cause) // 用給定的cause(原因)構造和描述信息來構造Throwable對象
Throwable initCause(Throwable cause) // 為這個對象設置原因,如果對象已有原因則拋出異常,返回this
Throwable getCause() // 獲取這個對象所設置的原因,如果沒有返回null
java.lang.Exception
Exception(Throwable cause)
Exception(String message, Throwable cause) // 用給定的cause(原因)來構建Exception對象