Java面向對象系列[v1.0.0][拋出異常]

程序出錯的時候,系統會拋出異常,這屬於被動拋出,而Java也允許採用一種主動拋出異常的方式或者說自行拋出異常,使用throw語句可以完成

使用throw拋出異常

throw拋出的不是異常類,而是一個異常實例,並且每次只能拋出一個異常實例,語法格式爲
throw ExceptionInstance;無論是系統自動拋出的異常還是開發者手動拋出的異常,Java運行時環境對異常的處理沒有任何差別

  • 如果throw語句拋出的異常是Checked異常,則該throw語句要麼處於try代碼塊裏,顯示捕獲該異常,要麼放在一個帶throws聲明拋出的方法中,也就是說把異常交給該方法的調用者處理
  • 如果throw語句拋出的異常時Runtime異常,則該語句無須放在try塊裏,也無須放在帶throws聲明拋出的方法中,程序既可以顯示的使用try…catch來捕獲並處理該異常,也可以完全不理會該異常,把該異常交給該方法調用者處理
public class ThrowTest
{
	public static void main(String[] args)
	{
		try
		{
			// 調用聲明拋出Checked異常的方法,要麼顯式捕獲該異常
			// 要麼在main方法中再次聲明拋出
			throwChecked(-3);
		}
		catch (Exception e)
		{
			System.out.println(e.getMessage());
		}
		// 調用聲明拋出Runtime異常的方法既可以顯式捕獲該異常,
		// 也可不理會該異常
		throwRuntime(3);
	}
	public static void throwChecked(int a) throws Exception
	{
		if (a > 0)
		{
			// 自行拋出Exception異常
			// 該代碼必須處於try塊裏,或處於帶throws聲明的方法中
			throw new Exception("a的值大於0,不符合要求");
		}
	}
	public static void throwRuntime(int a)
	{
		if (a > 0)
		{
			// 自行拋出RuntimeException異常,既可以顯式捕獲該異常
			// 也可完全不理會該異常,把該異常交給該方法調用者處理
			throw new RuntimeException("a的值大於0,不符合要求");
		}
	}
}

自定義異常

用戶自定義異常都應該繼承Exception基類,如果希望自定義Runtime異常,則應該集成RuntimeException基類,定義異常類時通常需要提供兩個構造器,一個是無參數的構造器,另一個是帶一個字符串參數的構造器,這個字符串將作爲該異常對象的描述信息(也就是異常對象的getMessage()方法的返回值)

public class AuctionException extends Exception
{
	// 無參數的構造器
	public AuctionException(){}       
	// 帶一個字符串參數的構造器
	public AuctionException(String msg)    
	{	
		// 通過super來調用父類的構造器,從而將此字符串參數傳給異常對象的message屬性
		// 該message屬性就是該異常對象的詳細描述信息
		super(msg);
	}
}

catch和throw同時使用

異常的處理方式大致有兩種

  • 在出現異常的方法內捕獲並處理異常,該方法的調用者將不能再次捕獲該異常
  • 該方法簽名中聲明拋出該異常,將該異常完全交給方法調用者處理
    然而在實際應用中往往沒有這麼簡單,單靠某個方法無法完全處理該異常,必須由幾個方法協作纔可完全處理該異常,換句話說,在異常出現的當前方法中,程序只對異常進行部分處理,還有些處理需要在該方法的調用者中才能完成,所以應該再次拋出異常,讓該方法的調用者也能捕獲到異常
    這種通過過個方法協作處理同一個異常的方式,可以通過在catch代碼塊中結合throw語句來完成
public class AuctionTest
{
	private double initPrice = 30.0;
	// 因爲該方法中顯式拋出了AuctionException異常,
	// 所以此處需要聲明拋出AuctionException異常
	public void bid(String bidPrice)
		throws AuctionException
	{
		var d = 0.0;
		try
		{
			d = Double.parseDouble(bidPrice);
		}
		catch (Exception e)
		{
			// 此處完成本方法中可以對異常執行的修復處理,
			// 此處僅僅是在控制檯打印異常跟蹤棧信息。
			e.printStackTrace();
			// 再次拋出自定義異常
			throw new AuctionException("競拍價必須是數值,"
				+ "不能包含其他字符!");
		}
		if (initPrice > d)
		{
			throw new AuctionException("競拍價比起拍價低,"
				+ "不允許競拍!");
		}
		initPrice = d;
	}
	public static void main(String[] args)
	{
		var at = new AuctionTest();
		try
		{
			at.bid("df");
		}
		catch (AuctionException ae)
		{
			// 再次捕捉到bid方法中的異常。並對該異常進行處理
			System.err.println(ae.getMessage());
		}
	}
}

增強的throw語句

對於如下代碼:

try
{
	new FileOutputStream("a.txt")
}
catch (Exception ex)
{
	ex.printStackTrace()'
	throw ex;
}

如下代碼所示,在try代碼塊中只調用了FileOutputStream構造器,這個構造器聲明只是拋出了FileNotFoundException異常,在Java7之前只需要聲明拋出Exception即可,而Java7之後會進行更仔細的檢查,當檢查到只可能拋出FileNotFoundException異常的時候,在方法簽名中只需要聲明拋出FileNotFoundException異常即可

import java.io.*;

public class ThrowTest2
{
	public static void main(String[] args)
		// Java 6認爲代碼throw ex可能拋出Exception,
		// 所以此處必須聲明拋出Exception
		// Java 7會檢查代碼throw ex可能拋出異常的實際類型,
		// 因此此處只需聲明拋出FileNotFoundException即可。
		throws FileNotFoundException
	{
		try
		{
			new FileOutputStream("a.txt");
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
			throw ex;        
		}
	}
}

異常鏈和異常轉譯

真正的企業級應用,有嚴格的分層,層與層之間有非常清晰的劃分,上層功能的實現嚴格依賴於下層的API,也不會跨層訪問
在這裏插入圖片描述
當業務邏輯層訪問持久層出現SQLException異常時,程序不應該把底層的SQLException異常傳到用戶界面,程序應該先捕獲原始異常,然後拋出一個新的業務異常,新的業務異常應該包含對用戶有用的的提示信息,這種處理方式叫做異常轉譯

public void calSal() throws SalException
{
	try
	{
		// 業務邏輯
		...
	}
	catch(SQLException sqle)
	{
		// 把原始異常記錄下來,留給管理員
		...
		// 下面異常中的message就是對用戶的提示
		throw new SalException("訪問底層數據庫出現異常")
	}
	catch (Exception e)
	{
		// 把原始異常記錄下來,留給管理員
		...
		// 下面異常中的message就是對用戶的提示
		throw new SalException("系統出現未知異常")
	}
}

這種把原始異常信息隱藏起來,僅向上提供必要的異常提示信息的處理方式,可以保證底層異常不會擴散到表現層,可以避免向上暴露太多的實現細節,這完全符合面向對象的封裝原則
這種把捕獲一個異常然後接着拋出另一個異常,並把原始異常信息保存下來是一種典型的鏈式處理,也被稱爲“異常鏈”
在JDK1.4以前,程序員必須自己編寫代碼來保持原始異常信息,從JDK1.4以後,所有Throwable的子類在構造器中都可以接收一個cause對象作爲參數,這個cause就用來表示原始異常,這樣可以把原始異常傳遞給新的異常,使得即使在當前位置創建並拋出新的異常,你也能通過這個異常鏈追蹤到一場最初發生的位置,例如希望通過上面的SalException去追蹤到最原始的異常信息,則可以將該方法改寫爲如下形式

public void calSal() throws SalException
{
	try
	{
		// 業務邏輯
		...
	}
	catch(SQLException sqle)
	{
		// 把原始異常記錄下來,留給管理員
		...
		// 下面異常中的sqle就是原始異常
		throw new SalException(sqle);
	}
	catch(Exception e)
	{
		// 把原始異常記錄下來,給管理員
		...
		// 下面異常中的e就是原始異常
		throw new SalException(e);
	}
}

上面程序中創建SalException對象時,傳入了一個Exception對象,而不是傳入了一個String對象,這就需要SalException類有相應的構造器,在jdk1.4之後,Throwable基類已有了一個可以接收Exception參數的方法,所以可以採用如下代碼來定義SalException類

public class SalException extends Exception
{
	public SalException(){}
	public SalException(String msg)
	{
		super(msg);
	}
	// 創建一個可以接受Throwable參數的構造器
	public SalException(Throwable t)
	{
		super(t);
	}
}

創建了這個SalException業務異常類後,就可以用它來封裝原始異常,從而實現對異常的鏈式處理

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