【java快速入門-異常處理篇】- 丟擲異常
技術標籤:java從入門到上升java異常處理丟擲異常java
異常的傳播
根據上一次部落格可知,當一個方法丟擲了異常,如果在本方法中沒有捕獲異常,那麼異常就會被拋到上層呼叫方法,直到遇到某個try.....catch被捕獲為止:
例如:
public class Main { public static void main(String[] args) { try { process1(); } catch (Exception e) { e.printStackTrace();//追蹤錯誤資訊 } } static void process1() { process2(); } static void process2() { Integer.parseInt(null); // 會丟擲NumberFormatException } }
輸出:
printStackTrace()
對於除錯錯誤非常有用,上述資訊表示:NumberFormatException
是在java.lang.Integer.parseInt
方法中被丟擲的,從下往上看,呼叫層次依次是:
main()
呼叫process1()
;process1()
呼叫process2()
;process2()
呼叫Integer.parseInt(String)
;Integer.parseInt(String)
呼叫Integer.parseInt(String, int)
。
然後通過給出的資訊,直接定位到pareseInt的原始碼,可以通過檢視原始碼的方式,找到錯誤的根本原因。
public static int parseInt(String s, int radix) throws NumberFormatException {
if (s == null) {
throw new NumberFormatException("null");
}
...
}
丟擲異常
當發生錯誤時,例如,使用者輸入了非法的字元,我們就可以丟擲異常。參考上面的Integer.parseInt()方法,丟擲異常一共分為兩步
- 建立某個
Exception
的例項; - 用
throw
語句丟擲。
例如:
void process2(String s) { if (s==null) { NullPointerException e = new NullPointerException(); throw e; } }
或者將他們合併
void process2(String s) {
if (s==null) {
throw new NullPointerException();
}
}
異常型別的轉換:捕獲異常之後,在catch中丟擲新的異常,例如
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {//在main()中捕獲IllegalArgumentException
e.printStackTrace();
}
}
static void process1() {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException();//當process2()丟擲NullPointerException後,被process1()捕獲,然後丟擲IllegalArgumentException()。
}
}
static void process2() {
throw new NullPointerException();
}
}
輸出
這樣輸出的資訊,無法定位到原始資訊。為了能追蹤到完整的異常棧,在構造異常的時候,把原始的Exception
例項傳進去,新的Exception
就可以持有原始Exception
資訊。對上述程式碼改進如下:
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}
static void process1() {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException(e);//在構造異常的時候,把原始的Exception例項傳進去
}
}
static void process2() {
throw new NullPointerException();
}
}
輸出
注意到Caused by: Xxx
,說明捕獲的IllegalArgumentException
並不是造成問題的根源,根源在於NullPointerException
,是在Main.process2()
方法丟擲的。在程式碼中獲取原始異常可以使用Throwable.getCause()
方法。如果返回null
,說明已經是“根異常”了。
前面提到finally在丟擲異常同樣也是適用的,catch
中丟擲異常,不會影響finally
的執行。JVM會先執行finally
,然後丟擲異常。
異常遮蔽(瞭解)
如果在執行finally
語句時丟擲異常
public class Main {
public static void main(String[] args) {
try {
Integer.parseInt("abc");
} catch (Exception e) {
System.out.println("process catched");
throw new RuntimeException(e);
} finally {
System.out.println("process finally");
throw new IllegalArgumentException();
}
}
}
這說明finally
丟擲異常後,原來在catch
中準備丟擲的異常就“消失”了,因為只能丟擲一個異常。沒有被丟擲的異常稱為“被遮蔽”的異常(Suppressed Exception)。
在極少數的情況下,我們需要獲知所有的異常。所以就是需要儲存所有的異常資訊,方法是先用origin
變數儲存原始異常,然後呼叫Throwable.addSuppressed()
,把原始異常新增進來,最後在finally
丟擲:
//異常遮蔽中獲取所有的異常
public class Main {
public static void main(String[] args) throws Exception {
Exception origin = null;
try {
System.out.println(Integer.parseInt("abc"));
} catch (Exception e) {
origin = e;//用origin變數儲存原始異常
throw e;
} finally {
Exception e = new IllegalArgumentException();//丟擲一個新的異常
if (origin != null) {
e.addSuppressed(origin);//Throwable.addSuppressed(),把原始異常新增進來
}
throw e;
}
}
}
當catch
和finally
都丟擲了異常時,雖然catch
的異常被遮蔽了,但是,finally
丟擲的異常仍然包含了它:
通過Throwable.getSuppressed()
可以獲取所有的Suppressed Exception
。
絕大多數情況下,在finally
中不要丟擲異常。因此,我們通常不需要關心Suppressed Exception
。
小結
呼叫printStackTrace()
可以列印異常的傳播棧,對於除錯非常有用;
捕獲異常並再次丟擲新的異常時,應該持有原始異常資訊;
通常不要在finally
中丟擲異常。如果在finally
中丟擲異常,應該原始異常加入到原有異常中。呼叫方可通過Throwable.getSuppressed()
獲取所有新增的Suppressed Exception
。