異常處理
使用Java異常處理機制:
把可能會發生錯誤的代碼放進try語句塊中。
當程序檢測到出現了一個錯誤時會拋出一個異常對象。
異常處理代碼會捕獲並處理這個錯誤。
catch語句塊中的代碼用於處理錯誤。
當異常發生時,程序控制流程由try語句塊跳轉到catch語句塊。
不管是否有異常發生,finally語句塊中的語句始終保證被執行。
如果沒有提供合適的異常處理代碼,JVM將會結束掉整個應用程序。
異常分類:
Throwable類有兩個直接子類:
Exception:出現的問題是可以被捕獲的;
Error:系統錯誤,通常由JVM處理。
可捕獲的異常又可以分為兩類:
(1)Check異常:直接派生自Exception的異常類,必須被捕獲或再次聲明拋出
(2)Runtime異常:派生自RuntimeException的異常類。使用throw語句可以隨時拋出這種異常對象: throw new ArithmeticException(…);
JDK1.4 以上提供了assert語句,允許程序在運行期間判斷某個條件是否滿足,不滿足時,拋出AssertionError,例如:
異常的“多態”特性
可以有多個catch語句塊,每個代碼塊捕獲一種異常。在某個try塊后有兩個不同的catch 塊捕獲兩個相同類型的異常是語法錯誤。
使用catch語句,只能捕獲Exception類及其子類的對象。因此,一個捕獲Exception對象的catch語句塊可以捕獲所有“可捕獲”的異常。
將catch(Exception e)放在別的catch塊前面會使這些catch塊都不執行,因為Java不會編譯這些catch塊。
“finally”的功用
資源泄露:當一個資源不再被某應用程序使用,但此程序並未向系統聲明不再使用此資源時發生這種情況
finally語句塊主要用於解決資源泄露問題,它位於catch語句塊之后,JVM保證它們一定執行。
注意:finally語句塊中也可能發生異常,如果這種情況發生,先前的異常被放棄。
動手動腦:多層的異常捕獲
比較兩個代碼的區別:
CatchWho1.java
1 public class CatchWho { 2 public static void main(String[] args) { 3 try { 4 try { 5 throw new ArrayIndexOutOfBoundsException(); 6 } 7 catch(ArrayIndexOutOfBoundsException e) { 8 System.out.println( "ArrayIndexOutOfBoundsException" + "/內層try-catch"); 9 } 10 throw new ArithmeticException(); 11 } 12 catch(ArithmeticException e) { 13 System.out.println("發生ArithmeticException"); 14 } 15 catch(ArrayIndexOutOfBoundsException e) { 16 System.out.println( "ArrayIndexOutOfBoundsException" + "/外層try-catch"); 17 } 18 } 19 }
結果:
catchwho2.java
1 public class CatchWho2 { 2 public static void main(String[] args) { 3 try { 4 try { 5 throw new ArrayIndexOutOfBoundsException(); 6 } 7 catch(ArithmeticException e) { 8 System.out.println( "ArrayIndexOutOfBoundsException" + "/內層try-catch"); 9 } 10 throw new ArithmeticException(); 11 } 12 catch(ArithmeticException e) { 13 System.out.println("發生ArithmeticException"); 14 } 15 catch(ArrayIndexOutOfBoundsException e) { 16 System.out.println( "ArrayIndexOutOfBoundsException" + "/外層try-catch"); 17 } 18 } 19 }
結果:
分析:catchwho1按照內外層的try catch代碼塊一步一步執行
catchwho2結果的原因是內存try里的異常並沒有被catch(ArithmeticException e)捕獲到,故該字段異常,不執行此try catch塊下的其他內容,而是此異常的ArrayIndexOutOfBoundsException()對象被外層的catch(ArrayIndexOutOfBoundsException e)捕獲,打印出相應結果。
辨析:finally語句塊一定會執行嗎?
1 public class SystemExitAndFinally { 2 public static void main(String[] args) 3 { 4 try{ 5 System.out.println("in main"); 6 throw new Exception("Exception is thrown in main"); 7 //System.exit(0); 8 } 9 catch(Exception e){ 10 System.out.println(e.getMessage()); 11 System.exit(0); 12 } 13 finally{ 14 System.out.println("in finally"); 15 } 16 } 17 }
結果:
結論:在exit(0)下, finally里的語句塊不會執行。
特別注意: 當有多層嵌套的finally時,異常在不同的層次拋出 ,在不同的位置拋出,可能會導致不同的finally語句塊執行順序。
如何跟蹤異常的傳播路徑?
當程序中出現異常時,JVM會依據方法調用順序依次查找有關的錯誤處理程序。
可使用printStackTrace 和 getMessage方法了解異常發生的情況: printStackTrace:打印方法調用堆棧。
每個Throwable類的對象都有一個getMessage方法,它返回一個字串,這個字串是在Exception構造函數中傳入的,通常讓這一字串包含特定異常的相關信息。
1 // UsingExceptions.java 2 // Demonstrating the getMessage and printStackTrace 3 // methods inherited into all exception classes. 4 public class PrintExceptionStack { 5 public static void main( String args[] ){ 6 try { 7 method1(); 8 } 9 catch ( Exception e ) { 10 System.err.println( e.getMessage() + "\n" ); 11 e.printStackTrace(); 12 } 13 } 14 public static void method1() throws Exception { 15 method2(); 16 } 17 public static void method2() throws Exception{ 18 method3(); 19 } 20 public static void method3() throws Exception{ 21 throw new Exception( "Exception thrown in method3" ); 22 } 23 }
輸出結果:
受控與不受控的異常
throws語句:throws語句表明某方法中可能出現某種(或多種)異常,但它自己不能處理這些異常,而需要由調用者來處理。 當一個方法包含throws子句時,需要在調用此方法的代碼中使用try/catch/finally進行捕獲,或者是重新對其進行聲明,否則編譯時報錯。
throws語句中聲明的異常稱為受控(checked)的異常,通常直接派生自Exception類。
RuntimeException(其基類為Exception) 和Error(基類為Throwable)稱為非受控的異常。這種異常不用在throws語句中聲明。
1 import java.io.*; 2 3 public class CheckedExceptionDemo { 4 public static void main(String[] args) { 5 try { 6 BufferedReader buf = new BufferedReader( 7 new InputStreamReader(System.in)); //拋出受控的異常 8 System.out.print("請輸入整數: "); 9 int input = Integer.parseInt(buf.readLine()); //有可能引發運行時異常 10 System.out.println("input x 10 = " + (input*10)); 11 } 12 //以下異常處理語句塊是必須的,否則無法通過編譯 13 catch(IOException e) { 14 System.out.println("I/O錯誤"); 15 } 16 //以下異常處理語句塊可以省略,不影響編譯,但在運行時出錯 17 catch(NumberFormatException e) { 18 System.out.println("輸入必須為整數"); 19 } 20 } 21 }
一個方法可以聲明拋出多個異常: int g(float h) throws OneException,TwoException { …… }
1 import java.io.*; 2 public class ThrowMultiExceptionsDemo { 3 public static void main(String[] args) { 4 try { 5 throwsTest(); 6 } 7 catch(IOException e) { 8 System.out.println("捕捉異常"); 9 } 10 } 11 private static void throwsTest() throws ArithmeticException,IOException { 12 System.out.println("這只是一個測試"); 13 // 程序處理過程假設發生異常 14 throw new IOException(); 15 //throw new ArithmeticException(); 16 } 17 }
在有繼承關系中,一個子類的throws子句拋出的異常,不能是其基類同名方法拋出的異常對象的父類。
自定義異常與異常處理鏈
介紹一種被廣泛使用的異常處理方法——通過自定義異常類捕獲並處理業務邏輯錯誤 。

1 class MyException extends Exception{ 2 public MyException(String Message) { 3 super(Message); 4 } 5 public MyException(String message, Throwable cause) { 6 super(message, cause); 7 } 8 public MyException(Throwable cause) { 9 super(cause); 10 } 11 } 12 public class ExceptionLinkInRealWorld { 13 public static void main(String args[]){ 14 try { 15 throwExceptionMethod(); //有可能拋出異常的方法調用 16 } 17 catch ( MyException e ){ 18 System.err.println( e.getMessage() +"--1"); 19 System.err.println(e.getCause().getMessage()+"--2"); 20 } 21 catch ( Exception e ){ 22 System.err.println("Exception handled in main--3" ); 23 } 24 doesNotThrowException(); //不拋出異常的方法調用 25 } 26 public static void throwExceptionMethod() throws MyException{ 27 try { 28 System.out.println( "Method throwException--4" ); 29 throw new Exception("系統運行時引發的特定的異--W"); // 產生了一個特定的異常 30 } 31 catch( Exception e ){ 32 System.err.println("Exception handled in method throwException--5" ); 33 //轉換為一個自定義異常,再拋出 34 throw new MyException("在方法執行時出現異常-W",e); 35 } 36 finally { 37 System.err.println("Finally executed in throwException--6" ); 38 } 39 // any code here would not be reached 40 } 41 public static void doesNotThrowException() 42 { 43 try { 44 System.out.println( "Method doesNotThrowException--7" ); 45 } 46 catch( Exception e ){ 47 System.err.println( e.toString() ); 48 } 49 finally { 50 System.err.println("Finally executed in doesNotThrowException--8" ); 51 } 52 System.out.println("End of method doesNotThrowException--9" ); 53 } 54 } 55 56 //45
結果:
在實際開發中,可以參照ExceptionLinkInRealWorld.java 示例的做法,定義一些與業務邏輯相關的自定義異常類,供上層代碼進行捕獲,從而能更精確地反映系統真實運行情況並及時進行處理。
關於開發中異常處理的建議
在中間層組件中拋出異常,在界面層組件中捕獲異常,在底層組件中捕獲JVM拋出的“只有程序員能看懂的”異常,轉換為中間層的業務邏輯異常,再由界面層捕獲以提供有意義的信息。
自身能夠處理的異常,不要再向外界拋出。
盡可能地在靠近異常發生的地方捕獲並處理異常。
盡可能地捕獲最具體的異常類型,不要在中間層用 catch(Exception)“吃掉”所有異常。
在開發階段捕獲並顯示所有異常信息,發布階段要移除部分代碼,以避免“過於專業”的異常信息困擾用戶,特別地,系統發布之后,不要將服務端異常的詳細信息發給客戶端,以免被黑客利用。