Java異常處理

Java異常處理

異常是運行時在代碼序列中引起的非正常狀態。在不支持異常處理的計算機語言中,必須手動檢查和處理錯誤,Java語言則採用面向對象的方式管理運行時錯誤。

一、概念

Java異常是用來描述在一段代碼中發生的異常情況的對象。當出現引起異常的情況時,就會創建用來表示異常的對象,並在引起異常的方法中拋出異常對象。方法可以選擇自己處理異常或者傳遞異常交由其他方法來處理。

Java異常處理通過五個關鍵字進行管理:try、catch、throw、throws、finally。

在try代碼快中封裝可能發生異常的程序語句,對這些語句進行監視。如果在try代碼塊中發生異常,就會將異常拋出。代碼使用catch來捕獲異常,並可以定義一些方法來處理異常。系統生成的異常由Java運行時系統自動拋出。爲了手動拋出異常,需要使用throw關鍵字。從方法中拋出的任何異常都必須通過一條throws子句進行指定。在try代碼塊結束之後必須執行的所有代碼則需要放在finally代碼塊中。

二、異常類型

所有異常類型都是內置類Throwable的子類,Throwable的兩個子類將異常分爲兩個不同的分支。一個分支是Exception類,這個類既可以用於用戶程序當前捕獲的異常情況,也可以用於創建自定義異常類型的子類。另一個分支是Error類,該類定義了在常規環境下不希望由程序捕獲的異常。Error類型的異常由Java運行時系統使用,以指示運行時系統本身發生了某些錯誤。

三、未捕獲的異常

沒有被程序捕獲的所有異常,最終都會交由Java運行時系統提供的默認處理程序捕獲。默認處理程序會顯示一個描述異常的字符串,輸出異常發生的堆棧蹤跡並終止程序。

例如,運行如下代碼:

public class Demo {

	public static void main(String[] args) {
		int a=2/0;
	}
	
}

生成的異常信息:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at Demo.main(Demo.java:5)

拋出的異常的類型是Exception的子類ArithmeticException,即算術異常,更具體地描述了發生的錯誤類型。Java提供了一些能與可能產生的各種運行時錯誤相匹配的內置異常類型。

四、使用try和catch

進行主動的異常處理有兩個優點:第一,語序允許修復錯誤;第二,阻止程序自動終止

爲了主動處理運行時錯誤,可以在try代碼塊中封裝希望監視的代碼,之後通過catch子句指定希望捕獲的異常類型。
例如,參照如下代碼:

	public static void main(String[] args) {
		try{
			int a=10/0;
			System.out.println("輸出語句1");
		}catch(ArithmeticException e){
			System.out.println("輸出語句2");
		}
		System.out.println("輸出語句3");
	}

輸出結果是:

	輸出語句2
	輸出語句3

代碼在進行除零操作時發生了異常,此時“輸出語句1”將無法得到被調用的機會。一旦拋出異常,程序控制就會從try代碼塊中轉移出來,進入catch代碼塊中。執行了catch語句後,就會繼續運行整個try/catch代碼塊的下一行。

五、多條catch子句

在有些情況下,一個代碼塊可能會引發多個異常。對於這種情況,需要指定兩條或多條catch子句,用於捕獲不同類型的異常。當拋出異常時,按順序檢查每條catch語句,執行類型和異常相匹配的第一條catch子句,忽略其他catch子句,並繼續執行try/catch代碼塊後面的代碼。

需要注意的是,異常子類必須位於異常超類之前,因爲使用了某個超類的catch語句會捕獲這個超類及其所有子類的異常。因此,如果子類位於超類之後的話,永遠也不會到達子類。不可到達的代碼會被編譯器提示錯誤。

參照如下代碼,通過Math的靜態方法random來隨機產生0和1兩個隨機數,生成的不同數值就會引發不同的異常,分別由不同的catch子句進行處理。

	public static void main(String[] args) {	
		int random=(int) Math.round(Math.random());
		try{
			int a=10/random;
			int[] array={10};
			array[random]=1;
		}catch (ArithmeticException e) {
			System.out.println("ArithmeticException");
		}catch (ArrayIndexOutOfBoundsException e) {
			System.out.println("ArrayIndexOutOfBoundsException");
		}
		System.out.println("代碼塊結束");
	}

此外,也可以通過多重捕獲的方式來使用相同的catch子句捕獲兩個或更多個異常。在catch子句中使用或運算符(|)分隔每個異常,每個多重捕獲參數都被隱式地聲明爲final類型。

    public static void main(String[] args) {    
        int random=(int) Math.round(Math.random());
        try{
            int a=10/random;
            int[] array={10};
            array[random]=1;
        }catch(ArithmeticException | ArrayIndexOutOfBoundsException e){
        	System.out.println("兩個異常之一");
        }
    }

六、throw

在之前的例子中,捕獲的異常都是由Java運行時系統拋出的異常,可以通過throw語句顯式地拋出異常。

throw的一般形式如下所示:

	throw ThrowableInstance

ThrowableInstance必須是Throwable或其子類類型的對象。throw語句之後的執行流程會立即停止,所有的後續語句都不會執行,然後按順序依次檢查所有的catch語句,檢查是否和異常類型相匹配。如果沒有找到匹配的catch語句,那麼默認的異常處理程序會終止程序並輸出堆棧蹤跡。

例如,運行以下代碼,將得到輸出結果:“NullPointerException”。

public class Demo {
	
	public static void demo(){
		throw new NullPointerException("NullPointer");
	}

	public static void main(String[] args) {
		try{
			demo();
		}catch (NullPointerException e) {
			System.out.println("NullPointerException");
		}
		
	}
	
}

但如果catch子句的異常與throw拋出的異常類型不匹配時,異常將由Java默認的異常處理程序來處理。

例如,運行以下代碼:

public class Demo {
	
	public static void demo(){
		throw new NullPointerException("NullPointer");
	}

	public static void main(String[] args) {
		try{
			demo();
		}catch (ArrayIndexOutOfBoundsException e) {
			System.out.println("ArrayIndexOutOfBoundsException");
		}
		
	}
	
}

運行結果是:

Exception in thread "main" java.lang.NullPointerException: NullPointer
	at Demo.demo(Demo.java:4)
	at Demo.main(Demo.java:9)

七、throws

如果方法可能引發自身不進行處理的異常,就必須通過throws關鍵字來向方法的調用者指明要拋出的異常類型。
方法可能拋出的所有異常都必須在throws子句中進行聲明,否則將產生編譯時錯誤。

例如,參照以下代碼,該程序試圖拋出無法匹配的異常,因爲程序沒有指定throws子句來聲明這一事實,所以程序無法編譯。

public class Demo {
	
	public static void demo(){
		throw new IllegalAccessException("IllegalAccess");
	}

	public static void main(String[] args) {
			demo();
	}
	
}

爲了使代碼能夠運行,需要將之進行如下修改:

public class Demo {
	
	public static void demo() throws IllegalAccessException{
		throw new IllegalAccessException("IllegalAccess");
	}

	public static void main(String[] args) {
			try {
				demo();
			} catch (IllegalAccessException e) {
				System.out.println(e.getMessage());
			}
	}
	
}

八、finally

當拋出異常後,方法的執行流程將不會按照原先的順序進行,這對於某些方法來說是個問題。例如,如果在方法中打開了一個文件,我們並不希望因爲拋出了異常導致關閉文件的代碼被跳過而不執行。finally關鍵字就是來解決這種情況的。
使用finally可以創建一個代碼塊,該代碼塊會在執行try/catch代碼塊之後執行,且在執行try/catch代碼塊後面的代碼之前執行。不管是否有異常拋出,都將執行finally代碼塊。

只要方法從try/catch代碼塊內部返回到調用者,不管是通過未捕獲的異常還是使用顯式的返回語句,都會在方法返回之前執行finally子句。
例如,如下的輸出結果將是:2

public class Demo {
	
	public static int demo(){
		try{
			int a=10/0;
			System.out.println(a);
		}catch(Exception e){
			return 1;
		}finally {
			return 2;
		}
	}

	public static void main(String[] args) {
		System.out.println(demo());
	}
	
}

九、鏈式異常

鏈式異常用於爲異常關聯另一個異常,第二個異常用於描述當前異常產生的原因。例如,某個方法從文件讀取數值來作爲除法運算的除數,由於發生了I/O錯誤導致獲取到的數值是0,從而導致了ArithmeticException異常。

如果想要讓代碼調用者知道背後的原因是I/O錯誤,使用鏈式異常就可以來處理這種情況以及其他存在多層異常的情況

爲了使用鏈式異常,Throwable有兩個構造函數和兩個方法用於處理這種情況。

兩個構造函數:

	Throwable(Throwable cause)

	Throwable(String message, Throwable cause)

cause即是用於指定引發當前異常的背後原因,message則可以用於同時指定異常描述。

兩個方法:

	Throwable getCause()

	Throwable initCause(Throwable cause)

getCause() 方法用來返回引發當前異常的異常,如果不存在背後異常則返回null。

initCause(Throwable cause) 方法將cause和明面上的異常關聯到一起,並返回對異常的引用。因此可以在創建異常之後將異常和背後異常關聯到一起。但是,背後異常只能設置一次,即initCause(Throwable cause) 方法只能調用一次。此外,如果通過構造函數設置了背後異常,也不能再使用該方法來設置背後異常了。

例如,參照如下代碼:

public class Demo {
	
	public static void demo(){
		NullPointerException nullPointerException=new NullPointerException("nullPointer");
		nullPointerException.initCause(new ArithmeticException("Arithmetic"));
		throw nullPointerException;
	}

	public static void main(String[] args) {
		try {
			demo();
		} catch (Exception e) {
			System.out.println(e.getMessage());
			System.out.println(e.getCause().getMessage());
		}
	}
	
}

運行結果是:

	nullPointer
	Arithmetic

鏈式異常可以包含所需要的任意深度,但是,過長的異常鏈可能是一種不良的設計。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章