本博客為個人原創,轉載需在明顯位置注明出處
這篇文章我們來聊聊捕獲異常的內容,如果一個方法內部拋出兩個或多個異常,外部捕獲的順序是怎樣的?捕獲到的異常,我們怎樣打印出他的棧軌跡?如果捕獲到異常之后不作任何處理再次拋出,他的棧軌跡會改變嘛?在項目里面我們經常會看到xxx Exception causedBy xxx Exception,這個是怎么回事?下面我們就來詳細討論一下(本篇代碼較多)
異常匹配###
class CatchHelper {
public void method(String arg1, String arg2) throws ParentException {
if (arg1 == null) {
throw new ChildException();
}
if (arg2 == null) {
throw new ParentException();
}
}
}
@Test
public void testCatchException() {
try {
String arg1 = null;
String arg2 = "";
CatchHelper helper = new CatchHelper();
helper.method(arg1, arg2);
} catch (ChildException e) {
System.out.println("Catch ChildException");
} catch (ParentException e) {
System.out.println("Catch ParentException");
} catch (Exception e) {
System.out.println("Catch Exception");
}
}
代碼中有兩個自定義異常,從名稱就可以明白,ChildException是繼承自ParentException。再看代碼,method方法中對兩個參數判空,分別拋出這兩個異常,按代碼中的異常捕獲,可以正常捕獲兩個異常。但是,如果將catch代碼塊中的ChildException和ParentException交換順序,在ChildException編譯器就會報錯,提示該異常時unreachable的。因為異常捕獲的順序是從上往下的,如果上面的異常匹配了,下面的異常就沒用了,代碼中ChildException是ParentException的子類,若ParentException放在catch代碼塊的第一位,無論是Child還是Parent都會被捕獲,排在第二位的ChildException就是無用的。總結成一句話就是:異常捕獲要從上到下,從小到大
棧軌跡###
打印異常棧軌跡很簡單,就是e.printStackTrace()方法的調用,打印出的是從異常拋出點的方法名到系統最底層的方法棧信息,不多解釋,沒有試過的可以自己去試下
重新拋出異常###
class RethrowHelper {
public void methodA() throws MyException {
System.out.println("this is methodA()");
throw new MyException("thrown from methodA()");
}
public void methodB() throws MyException {
try {
methodA();
} catch (MyException e) {
System.out.println("Catch MyException in catch block from methodB()");
// e.printStackTrace();
// 直接拋出捕獲到的異常
throw e;
}
}
public void methodC() throws MyException {
try {
methodA();
} catch (MyException e) {
System.out.println("Catch MyException in catch block from methodC()");
// e.printStackTrace();
// 替換捕獲到的異常的棧路徑,然后再拋出
e.fillInStackTrace();
throw e;
}
}
}
@Test
public void testRethrow() {
RethrowHelper helper = new RethrowHelper();
try {
helper.methodB();
} catch (MyException e) {
System.out.println("Main printStackTrace");
e.printStackTrace();
}
try {
helper.methodC();
} catch (MyException e) {
System.out.println("Main printStackTrace");
e.printStackTrace();
}
}
從代碼中可以看出,methodB方法捕獲到異常之后直接拋出,methodC方法捕獲到methodA的異常之后,調用了e.fillInStackTrace()方法,然后再拋出。區別在于,如果直接拋出,異常的棧信息還是保持不變,也就是說testRethrow測試方法中打印出的methodB的異常信息仍然是從methodA拋出的。若像methodC一樣,調用了e.fillInStackTrace()方法再拋出,異常就會先把當前方法作為棧頂信息,那么在外部testRethrow測試方法中捕獲到的棧信息就是從methodC拋出的。光文字解釋看起來可能有點費解,直接看運行結果:
testRethrow測試方法捕獲到的methodB的異常信息還是從methodA拋出的,異常信息沒變
methodC的異常信息就是從methodC拋出的,之前methodA的信息不存在了,看出區別了吧?
異常鏈###
class CauseHelper {
public void setValue(String value) throws ValueValidException {
if (value == null) {
ValueInvalidException exception = new ValueInvalidException();
// 將空指針異常設置為ValueInvalidException的起因,即CausedBy ......
exception.initCause(new NullPointerException());
throw exception;
}
}
}
@Test
public void testCause() {
CauseHelper helper = new CauseHelper();
try {
helper.setValue(null);
} catch (ValueValidException e) {
e.printStackTrace();
}
}
在開發過程中,我們經常會看到logcat上打印出的日志有兩個異常信息,中間由causedBy連接,如上述代碼的運行結果所示:
setValue方法中判斷value如果為空,就拋出一個自定義的ValueInvalidException,接著調用了異常對象的initCause方法,傳入了一個NullPointerException作為參數,不難理解,這是給拋出的異常添加一個因果關系,說明我拋出的ValueInvalidException是由NullPointerException導致的,這樣的異常信息更加專業全面。
關于捕獲異常就聊到這里,下一篇要分享的是運行時多態中的異常,歡迎拍磚!