JAVA異常處理

淵渟嶽發表於2022-02-24

 

異常的執行過程理解:

1如果程式在執行過程中出現了異常後,那麼會由JVM自動根據異常的型別例項化一個與之型別匹配的異常類物件(此處使用者不用去關心new,由系統自動負責處理)
2、產生了異常物件之後會判斷當前語句上是否存在有異常處理,如果沒有異常處理,那麼就交給JVM進行預設的異常處理(輸出異常資訊,而後結束結束程式的呼叫)
3、如果此時存在有異常操作,那麼會由try語句來捕獲產生的異常類例項化物件,而後先後與catch語句(自上而下)引數進行匹配,如果匹配成功,則先執行fianlly程式碼體,後執行catch程式碼體,然後繼續執行finally後面的程式碼,此時就算是處理過引數所代表的異常了但是如果匹配不成功,則會執行fianlly語句,但是後面的程式就不會執行了。
4、所以對於所有的catch(**),相當於方法的過載,可以使用向上轉型來確定catch引數是Exception類的例項化物件,省去程式碼量(即直接以父類的例項化物件作為引數,代表所有的子類),不然就得具體寫出具體的子異常類,也就是說所有的異常可以使用一種處理方式,但是最好分開處理

使用finally回收資源:

有些時候,程式在try塊裡面開啟了一些物理資源(例如資料庫連線、網路連線和磁碟檔案等),這些物理資源都必須顯式回收。(Java的垃圾回收機制不會回收任何的物理資源,垃圾回收機制只能回收堆記憶體中的物件所佔用的記憶體)。如果在try或者catch塊裡進行回收,則可能會導致部分程式碼因為異常的出現,在try中某條發生異常的語句之後的程式碼都不會被執行(包括可能的資源回收語句),如果catch語句中進行資源回收,但是catch塊完全有可能不被執行。

所以finally塊可以完全用來回收資源。不管try塊中的程式碼是否出現異常,也不管哪一個catch塊被執行,只要在try或catch塊中沒有呼叫退出JVM的方法,finally塊總會被執行。

package com.java.exception;
import java.io.FileInputStream;
import java.io.IOException;
/**
* @author jack.chen
* @version 建立時間:2018年7月29日 下午2:21:34
* 類說明:
*/
public class TestFinally {
	public static void main(String[] args) {
		FileInputStream fis = null;
		try 
		{
			fis = new FileInputStream("a.txt");  //從檔案a.txt中讀取位元組到輸入流,如果a.txt檔案找不到,則fis值還是null
		}
		catch(IOException ioe) 
		{
			System.out.println(ioe.getMessage());
			return; //利用return語句強制main方法返回,但是先會執行finally塊再返回(在方法返回之前執行finally塊)
			//System.exit(1);//使用exit來退出虛擬機器 ,如果註釋掉上面的return語句,執行本句,即在異常處理的catch塊中
			//使用System.exit(1)來退出JVM,則finally將失去執行的機會。所以只要在try或catch塊中呼叫了退出了JVM的方法,
			//finally塊就不會執行。
		}
		finally  //異常處理巢狀,catch、try也可以
		{
			if(fis!=null)  //如果a.txt檔案存在 
			{
				try 
				{
					fis.close();  //Java垃圾回收機制只會回收堆記憶體中的物件所佔用的記憶體
					//關閉磁碟檔案,避免資源浪費在其他不必要的地方,回收在try塊中開啟的物理資源
				}
				catch(IOException ioe)
				{
					ioe.printStackTrace();
				}
			}
			System.out.println("程式已經執行了finally裡的資源回收!");
		}
		
	}
}
//Output:
//a.txt (系統找不到指定的檔案。)
//程式已經執行了finally裡的資源回收!

 

try-catch-finally執行順序

異常處理語法結構中,只有try塊是必須的,catch塊和finally塊至少出現其一。
1當try沒有捕獲到異常時: try語句塊中的語句逐一被執行,程式將跳過catch語句塊,執行finally語句塊和其後的語句;
2當try捕獲到異常時

catch語句塊裡沒有處理此異常的情況:當try語句塊裡的某條語句出現異常時,而沒有處理此異常的catch語句塊時,此異常將會拋給JVM預設處理,finally語句塊裡的語句還是會被執行,但finally語句塊後的語句不會被執行;
②catch語句塊裡有處理此異常的情況:在try語句塊中是按照順序來執行的,當執行到某一條語句出現異常時,程式將跳到catch語句塊,並與catch語句塊逐一匹配,找到與之對應的處理程式,其他的catch語句塊將不會被執行,而try語句塊中,出現異常之後的語句也不會被執行,catch語句塊執行完後,再執行finally語句塊裡的語句,最後執行finally語句塊後的語句

 注意:

**try ****塊:**用於捕獲異常。其後可接零個或多個catch塊,如果沒有catch塊,則必須跟一個finally塊。
**catch ****塊:**用於處理try捕獲到的異常。
**finally ****塊:一般來說finally中的方法都是會被執行的,其中finally中很大程度上用於資源的釋放。
無論是否捕獲或處理異常,finally塊裡的語句都會被執行。但在try塊或catch塊中遇到return語句時,finally語句塊將在方法返回之前被執行。
在以下4種特殊情況下,finally塊不會被執行:
                    1)在finally語句塊中發生了異常。
                    2)在前面的程式碼中用了System.exit(0)退出程式。
                    3)程式所在的執行緒死亡。
                    4)關閉CPU。

 

注意:

    通常情況下,不要使用return 或者throw等導致方法終止的語句,一旦在finally塊中使用了return或throw語句,
將會導致try塊、catch塊中的return、throw語句(一一對應)失效。

package com.java.exception;
/**
* @author jack.chen
* @version 建立時間:2018年7月29日 下午3:05:56
* 類說明:
*/
public class TestFinallyFlow 
{
	public static void main(String[] args) throws Exception
	{
		boolean a = test(); 
		System.out.println(a);  //false
	}
	public static boolean test() {
		try //失效
		{
			return true;
		}
		finally 
		{
			return false;
		}
	}
}

當程式執行try塊、catch塊時遇到了return 語句或throw語句,這兩個語句都將導致該方法立即結束。所以系統並不會立即執行這兩個語句,而是去尋找該異常處理流程中是否包含了finally塊。如果沒有,則執行return或throw語句,終止方法的執行,如果有,則立即開始執行finally塊,如果finally塊中沒有return或throw語句,系統就會再次跳回來執行try塊、catch塊裡的return或throw語句。如果遇到了finally中有return或throw語句,則在finally塊中終止方法,不會跳回來執行try塊、catch塊裡的return或throw語句了。

Error和Exception和異常的區別與聯絡:

1、在編寫多個catch異常中的引數問題,捕獲範圍要先小後大(如果先大後小,則後面的“小”則永遠沒機會得到執行)

2所有的異常類都是Throwable的子類,而在Throwable下有還有一個子類Error
       Error:指的是JVM錯誤,Java虛擬機器無法解決的JVM系統內部錯誤、資源耗盡等嚴重問題,一般不編寫針對性的程式碼進行處理。即此時的程式還沒有執行,使用者無法處理。如棧溢位: java.lang.StackOverflowError
        Exception:指的是程式執行中產生的異常,即其他因程式設計錯誤或者是偶然的外在因素導致的一般性問題,使用者可以可以使用針對性的程式碼進行處理。如:空指標訪問,試圖讀取不存在的檔案,網路連線中斷

3、 (異常跟蹤棧資訊如:at java.io.FileInputStream open...)

所有的異常物件都包含了如下的幾個常見用法:
getMessage():返回改異常的詳細描述字串;
printStackTrace():返回該異常的跟蹤棧資訊,輸出到標準錯誤輸出流中;
printStackTrace(PrintStream s):將該異常的跟蹤棧資訊,輸出到指定的輸出流中;
getStackTrace():返回該異常的跟蹤棧資訊

 

throws關鍵字:

package com.java.exception;
/**
* @author jack.chen
* @version 建立時間:2018年7月27日 上午12:31:09
* 類說明:throws關鍵字,用於方法宣告,指的是此方法在被呼叫的過程中產生的異常交由呼叫此方法的地方處理
* 
*/

class test{
	public static int div(int x, int y)throws Exception /*丟擲異常類,異常類可以有多個,用逗號隔開*/{
		return x/y;  //使用throws宣告丟擲異常的方法,無須使用try...catch
	}
}
public class Test4 {
	public static void main(String[] args) {
		try {
		System.out.println(test.div(10, 2));
		}catch(Exception e) {
			e.printStackTrace();
		}	
	}
}
//主方法使用throws關鍵字,如果發生了異常,則異常會拋給JVM處理,採用預設的異常處理方式,輸出異常,然後結束程式的執行
	//主方法不用throws,因為程式一旦出錯,也希望程式能夠執行完畢
	//public static void main(String[] args) throws Exception {
	//	System.out.println(test.div(10, 0));
	//}

注意:

       使用throws關鍵字宣告丟擲異常時有一個和限制:就是方法重寫時的“兩小”中的一條規則:子類方法中宣告丟擲的異常型別應該是 父類方法中宣告丟擲的異常型別的子類或者相等。子類方法中不允許比父類方法宣告丟擲更多更大的異常。

 

throw關鍵字: 

public class Test5 {
	public static void main(String[] args) {
		try {
			throw new Exception("自己定義的異常!");//利用Exception構造方法
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

 再次編譯:編譯通過、執行成功

如果throw語句丟擲的是Checked異常,·則該throw語句要麼位於try塊裡,顯式捕獲該異常,要麼放在一個帶throws宣告丟擲的方法中,即把該異常交給該方法的呼叫者處理。如果是Runtime異常,則該throw語句無須位於try塊裡,顯式捕獲該異常,也無須放在一個帶throws宣告丟擲的方法中,程式既可以使用try...catch來捕獲,並處理該異常,也可以不處理該異常。

package com.java.exception;
/**
* @author jack.chen
* @version 建立時間:2018年7月29日 下午3:48:15
* 類說明:
*/
public class TestThrow {
	public static void main(String[] args) {
		try 
		{
			throwChecked(-3);//呼叫帶throws宣告的方法,必須顯式捕獲該異常,否則,必須在main方法中再次宣告丟擲
		}
		catch(Exception e) 
		{
			System.out.println(e.getMessage());
		}
		throwRuntime(3);//呼叫丟擲Runtime異常類的方法,既可以顯式捕獲該異常,也可以不理會該異常
	}
	public static void throwChecked(int a)throws Exception{
		if(a>0)  
			throw new Exception("a的值大於0,不符合要求!"); //自行丟擲Exception異常,該程式碼位於try塊裡,或處於帶throws宣告的方法中
	}
	public static void throwRuntime(int a){
		if(a>0)
			throw new RuntimeException("a的值大於0,不符合要求!");//自行丟擲RuntimeException異常,既可以顯式捕獲該異常,也可以完全不用理會該異常,把該異常交給呼叫該方法的呼叫者處理
	}
}

下面給出一個經典的自定義異常

package com.java.exception;
/**
* @author jack.chen
* @version 建立時間:2018年7月29日 上午12:01:01
* 程式功能:使用throw關鍵字自定義異常
*/
class MyException extends Exception   //自定義異常類要繼承自Exception或RuntimeException
{
	String message;
    public MyException(){}  //定義一個無參構造器
	public MyException(String ErrorMessagr) {
		message = ErrorMessagr; 
        //通常使用super(**);
	}
	public String getMessage() //定義一個重寫getMessage()的方法
	{
		return message;
	}
}
public class Test7 
{
	public static int div(int x, int y) throws MyException //會產生異常,要拋異常
	{
		if(y<0) 
		{
			throw new MyException("除數不能為負數"); //產生異常則(向呼叫此方法的main函式)丟擲一個new的MyException例項化物件
		}
		return x/y;
	}
	public static void main(String[] args) {
		try 
		{
			int result = div(3,-1);//主方法中呼叫方法,會接收一個new的MyException例項化物件,轉於catch語句進行異常類的匹配
		}catch(MyException e) {
			System.out.println(e.getMessage());//輸出異常資訊
		}catch(ArithmeticException e) {
			System.out.println("除數1不能為0"); 
		}catch(Exception e) {
			System.out.println("程式發生了其他的異常");
		}
	}
}

 

 

 

 

RumtimeException異常類:

java異常類可以分為兩大體系:

Checked異常和Runtime異常體系,而所有的RuntimeException類及其子類的例項都被稱為Runtime異常,其他的都是Checked異常。

先來觀察函式parseInt()方法的原型:

public Integer(String s) throws NumberFormatException
public class Test8 {
	public static void main(String[] args) {
		int x = Integer.parseInt("100");
	}
}

 但是上面的程式碼編譯執行是可以通過的。按道理來講應該強制性的捕獲異常,但是現在並沒有這種捕獲異常的try...catch語句。根據異常的知識,如果一個方法丟擲了異常,就應該在呼叫此方法的方法中定義try...catch程式碼語句,但是這裡在呼叫parseInt()方法中的main函式中並沒有try...catch語句。

開啟API,會發現NumberFormatException異常類的繼承結構如下:
java.lang.Object
   java.lang.Throwable
      java.lang.Exception
         java.lang.RuntimeException
            java.lang.IllegalArgumentException
               java.lang.NumberFormatException
所以,發現了NumberFormatException異常類是RuntimeException異常類的間接子類。
而如 ArithmeticException,NullPointerException,以及ClassCastException都是常見的RuntimeException直接子異常類。
實際上,在Java中,為了方便使用者程式碼的編寫,專門提供了RuntimeException異常類,這種異常類的最大特徵在於:
如果程式在編譯的時候不會強制性地要求使用者處理異常,使用者可以選擇性地處理異常。如果發生了異常,
但是如果沒有進行處理,就交給JVM進行預設處理(即輸出異常,然後結束程式的執行)。
也就是說RuntimeException的子異常類,可以由使用者根據自己的需要選擇性地進行異常的處理。
而作為RuntimeException的父類Exception異常類,如果發生了異常就必須有try...catch語句,也就是進行異常的處理。
注意:對於RuntimeException的子類最好也使用異常處理機制。雖然RuntimeException的異常可以不使用try...catch進行處理,
但是如果一旦發生異常,則肯定會導致程式中斷執行,所以,為了保證程式再出錯後依然可以執行,在開發程式碼時最好使用try...catch的異常處理機制進行處理。

 

 

一種重要的程式碼模型:

class T{
	public static int div(int x, int y) {
		int result = 0;
		System.out.println("***1、除法運算開始 ***");
		result = x/y;
        System.out.println("***2、除法運算結束 ***");
		return result;
	}
}
public class Test6 {
	public static void main(String[] args) {
		try {
			System.out.println(T.div(12, 0));
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

編譯執行結果是:

 

可見一出現異常, 處於發生異常程式碼句之後的程式碼句

System.out.println("***2、除法運算結束 ***");
return result;

就不會執行了,如果此時我們需要讓它們執行呢,那麼就可以將x/y語句作為try的程式碼塊:

package com.java.exception;
/**
* @author jack.chen
* @version 建立時間:2018年7月28日 上午12:03:01
* 類說明:throw關鍵字通常用在方法體中,並且丟擲一個異常物件。程式在執行到throw語句時立即停止,它後面的語句都不執行。
* 通過throw丟擲異常後,如果想在上一級程式碼中來捕獲並處理異常,則需要在丟擲異常的方法中使用throws關鍵字在方法宣告中指明要丟擲的異常;
* 如果要捕捉throw丟擲的異常,則必須使用try—catch語句。
*/
class T{
	public static int div(int x, int y) throws Exception {
		int result = 0;
		System.out.println("***1、除法運算開始 ***");/*相當於把門關上*/
		try{
			result = x/y; 
			/*產生異常之後,就執行try程式碼塊,判斷catch引數是否有相關的異常類,如果有的話可以執行catch的程式碼體,
			但是由於後面有finally程式碼塊,所以先執行finally程式碼塊(不管出不出現異常都會執行finally語句),然後才執行catch()
			程式碼體,所以此處定義的throw e,指的是如果匹配到了異常類Exception的例項物件e,就吧把異常向上拋到"throws Exception"
			關聯的(呼叫此div()方法的main函式中的catch語句匹配例項化異常類物件)*/
		}catch(Exception e) {
			throw e;
		} finally {
			System.out.println("***2、除法計算結束 ***"); /*finally程式碼塊,是無論中途會不會出現異常,都會執行的,相當於把門關上*/
		}
		return result;
	}
}
public class Test6 {
	public static void main(String[] args) {
		try {
			System.out.println(T.div(12, 0));
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}

編譯執行:

 實際上,以上程式碼可以縮寫,即:將div函式中的catch語句刪除,即不處理出現的異常,就直接將異常拋給呼叫div方法的main函式進行異常處理。