Java異常之throws和throw 以及異常使用注意的原則
一.使用throws丟擲異常
如果在當前方法不知道該如何處理該異常時,則可以使用throws對異常進行丟擲給呼叫者處理或者交給JVM。JVM對異常的處理方式是:列印異常的跟蹤棧資訊並終止程式執行。
throws在使用時應處於方法簽名之後使用,可以丟擲多種異常並用英文字元逗號’,’隔開
e.g.1
public void throwsTest() throws ExceptionClass1, ExceptionClass2 {...}
如果丟擲給呼叫者的異常是Checked異常,這種異常是我們需要處理以來提高程式健壯性的,一般丟擲則要呼叫者做相應處理,要麼呼叫者對該異常進行try…catch處理,要麼再次throws交給上一層。這其中需要注意一點:子類方法宣告丟擲的異常型別應是父類方法宣告丟擲的異常型別的子類或相同,子類方法宣告跑出的異常不允許比父類方法宣告丟擲的異常多。
e.g.2
public class ThrowsTest2{
//因為test();會丟擲IOException,main方法是呼叫者
//則需要使用try...catch進行捕獲或者拋給JVM
//丟擲時要遵循父類宣告丟擲“大於”子類宣告丟擲的原則
public static void main(String[] args) throws Exception {
test();
}
public static void test() throws IOException() {
//因為FileInputStream構造器會丟擲IOException
//所以需要使用try...catch塊進行處理或使用throws拋給呼叫者
FileInputStream fis = new FileInputStream("a.txt");
}
}
二.使用throw丟擲異常
如果需要程式在程式中自行丟擲異常,應該使用throw語句丟擲,丟擲的不是一個類而是一個物件且只能丟擲一個物件。它可以單獨使用,也可以結合catch塊捕獲使用。如果丟擲的異常物件時Checked異常則處於try塊裡被catch捕獲或者放在一個帶throws的方法裡;如果丟擲的是RuntimeException則既可以顯示使用try…catch捕獲也可以不理會它
e.g.3
public class ThrowTest {
public static void main(String[] args) {
try{
throwChecked(3);
}catch(Exception e) {
System.out.println(e.getMessage());
}
throwRuntime(-3);
}
//該方法內丟擲一個Exception異常物件,必須捕獲或拋給呼叫者
public static void throwChecked(int a) throws Exception {
if(a < 0) {
throw new Exception("a的值應大於0,不符合要求")
}
}
//該方法內丟擲一個RuntimeException物件,可以不理會直接交給JVM處理
public static void throwRuntime(int a) {
if(a < 0) {
throw new RuntimeException("a的值應大於0,不符合要求")
}
}
}
Java7增強的throw語句:
在Java7之前,父類和子類在宣告丟擲異常時應符合父類包含的異常“大於等於”子類包含的異常的規則;從Java7開始,Java編譯器會檢查throw語句丟擲的異常的實際型別,如下程式碼中編譯器知道throw e只能丟擲FileNotFoundException 所以在方法簽名上可以直接寫該異常:
e.g.4
public class ThrowTest2 {
public static void main(String[] args) throws FileNotFoundException {
try {
new FileOutputStream("a.txt");
}catch(Exception e) {
e.printStackTrace();
throw e;
}
}
}
三.自定義異常類
在丟擲異常時,異常類名往往包含有用的資訊,所以在選擇丟擲異常時需要選擇適合的類,從而可以明確的描述該異常情況。這時候就需要我們自己定義異常,自定義異常一定是Throwable的子類,若是檢查異常就要繼承自Exception,若是執行時異常就要繼承自RuntimeException
e.g.5
public class AuctionException extends Exception {
//無參構造
public AuctionException() {}
//含參構造
//通過呼叫父類的構造器將字串msg傳給異常物件的massage屬性,
//massage屬性就是對異常的描述
public AuctionException(String msg) {
super(msg);
}
}
四.catch和throw同時使用
在實際應用中,在異常出現的當前方法中,程式只能對異常做部分處理,還有些處理需要在該方法的呼叫者才能夠完成,使用需要再次丟擲異常。這時,就需要將catch和throw結合使用。
e.g.6
public class AuctionTest {
private double initPrice = 30.0;
public void bid(String bidPrice) throws AuctionException {
double 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) {
AuctionTest at = new AuctionTest();
try{
at.bid("..");
}catch(AuctionException ae) {
System.out.println(ae.getMessage());
}
}
}
以上程式碼的執行結果為:
競拍價必須為數值,不能包含其它數值
java.lang.NumberFormatException: multiple points
at sun.misc.FloatingDecimal.readJavaFormatString(Unknown Source)
at sun.misc.FloatingDecimal.parseDouble(Unknown Source)
at java.lang.Double.parseDouble(Unknown Source)
at ExceptionTest.AuctionTest.bid(AuctionTest.java:15)
at ExceptionTest.AuctionTest.main(AuctionTest.java:28)
五.異常鏈
把捕獲一個異常然後接著丟擲另一個異常,並把原始異常資訊儲存下來是一種典型的鏈式處理,也被稱作異常鏈。
六.Java的異常跟蹤棧
如e.g.6的執行結果
除去第一行列印的自定義的提示資訊之外的第一行是異常的型別和詳細訊息,之後的提示資訊中含有確切程式碼行數的資訊記錄了所有的異常發生點,各行顯示被呼叫方法中執行的停止位置。跟蹤棧總是最內部的被呼叫方法逐漸上傳知道外部業務操作的起點,通常就是main方法或者Thread的run方法。
七.異常處理規則
- 不要過度使用異常:對於完全已知的錯誤應編寫處理這種錯誤程式碼從而提高程式碼的健壯性,只有外部的、不能確定的和不可預知的執行時錯誤使是用異常,並且異常機制的效率低於正常的流程控制。
- 不要使用過於龐大的try塊:過於龐大的try塊業務也相對更復雜,會導致try塊中異常的可能性大大增加,在分析發生異常的原因時難度增加。
- 避免使用Catch All語句:Catch All是catch(Throwable t),也會在發生異常是分析原因的複雜度增加。
- 不要忽略已捕獲到的異常:對於捕獲到的異常應該對其進行處理從而提高程式碼健壯性,而不是什麼都不做或者只是列印跟蹤棧資訊。