程序出錯的時候,系統會拋出異常,這屬於被動拋出,而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業務異常類後,就可以用它來封裝原始異常,從而實現對異常的鏈式處理