異常(Exception)

AskiaYaten發表於2020-10-20

Java學習筆記——異常(Exception)

異常的分類

img

Throwable類

Throwable:所有異常都是由Throwable繼承而來的,可以通過繼承Throwable來實現新的異常,但是一般不推薦這樣做,下一層分為了兩個分支:ErrorException

Error類

Error類來用描述java執行時系統內部引起的錯誤和資源消耗錯誤,因為是java內部的錯誤,因此編寫的應用程式無能為力。

Exception類

Exception:Exception類又可以分為IOExceptionRuntimeException,一般程式應該通過檢測的方式儘量避免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

注意這裡特別強調宣告檢查型異常,對於丟擲非檢查型異常一般是不需要宣告的

什麼時候需要宣告

兩種情況:

  1. 呼叫一個丟擲檢查型異常的方法的時候,傳遞異常的丟擲,例如呼叫loadImage方法
  2. 方法本身需要丟擲檢查型異常
// 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

一般怎樣決定丟擲異常呢?通常考慮的情況是這樣:

  1. 找到一個合適的異常類
  2. 建立這一個異常類的物件
  3. 將物件丟擲

一旦丟擲異常之後,這個方法不會返回到呼叫者,也就是說無需要另外建議一個返回語句(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);
	}
}
// 這樣就可以完成自己的異常的定義了

異常鏈與異常再次包裝

異常巢狀

先看下面的程式碼:

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物件

相關文章