標籤:異常處理 java
java的異常處理機制可以使程式有極好的容錯性,讓程式更加的健壯.所謂的異常,就是指的阻止當前方法或範圍繼續執行的問題,,當程式運行時出現異常時,系統就會自動產生一個Exception對象來通知程式.這樣就極大的簡化了我們的工作.
當然java的異常對象有很多種,下面這幅圖顯示了java異常類的繼承體系.
從圖片中可以看到java將所有的非正常情況分成了兩種: 異常(Exception)和錯誤(Error),他們都繼承Throwable父類.Error錯誤一般是指與虛擬機器相關的問題,如系統崩潰,虛擬機器錯誤等.這種錯誤無法恢複或不可捕獲,將導致應用程式中斷,通常應用程式無法處理這些錯誤,因此也不應該試圖用catch來進行捕獲.而對於Exception我們是可以進行捕獲並處理的.下面從幾個方面對異常處理機制進行介紹.
一.異常處理的一般格式
try{ ///可能會拋出異常的代碼 } catch(Type1 id1){ //處理Type1類型異常的代碼 } catch(Type2 id2){ ///處理type2類型異常的代碼 }
try塊中放置可能會發生異常的代碼(但是我們不知道具體是哪種異常).如果異常發生了,try塊拋出系統自動產生的異常對象,然後異常處理機制將負責搜尋參數與異常類型相匹配的第一個處理常式,然後進行catch語句執行(不會在向下尋找).看起來和switch語句比較類似.除了上面列出的那些異常類,其實我們也可以自己定義異常,然後處理他們.定義異常最重要的是異常類的名字,最好做到望名知義.另外異常處理中經常出現的throws和throw這兩個關鍵字;throws表示的是當前程式不處理這個異常將其拋給調用這個程式的上一級程式執行,如果是main()方法則傳給JVM虛擬機器處理.throw表示的是顯式的拋出一個異常對象.下面的代碼中用到了上面提到的知識點.
///自己定義一個簡單的異常類,只需要聲明以下繼承關係///也可以自己編寫構造器,但這不是必要的public class SimpleException extends Exception{ ///public SimpleException(); ///public SimpleException(String str){ ///super(str)};}package lkl;import java.util.Scanner;public class ExceptionTest { ///聲明該程式不處理SimpleException異常而是將其丟出 public void f() throws SimpleException{ System.out.println("Throws SimpleException from f()"); ///丟出一個SimpleException異常對象 throw new SimpleException(); } ///多個try語句會依次執行,即使上一個try語句發生異常,下一個try語句任然會執行 public static void main(String[] args){ ExceptionTest et =new ExceptionTest(); try{ et.f(); }///處理try中丟出的SimpleException異常 catch (SimpleException sn){ System.out.println("Catch it"); //調用異常類的printStackTrace()方法,預設是列印到標準錯誤流的 ///現在我們顯示指定輸出到標準輸出資料流 ///會列印從方法調用處直到異常拋出處的方法調用序列,棧頂是最後一次調用的而棧底是第一次掉用的 sn.printStackTrace(System.out); } try{ int[] a= new int[10]; Scanner sc = new Scanner(System.in); System.out.println("請輸入k: "); int k=sc.nextInt(); sc.close(); System.out.println(a[k]); } ///處理數組下標越界的異常 catch(ArrayIndexOutOfBoundsException ae){ System.out.println("Catch ArrayIndexOutOfBoundsException"); ae.printStackTrace(System.out); } }}
二.列印異常資訊
異常類的基類Exception中提供了一組方法用來擷取異常的一些資訊.所以如果我們獲得了一個異常對象,那麼我們就可以列印出一些有用的資訊,最常用的就是void printStackTrace()這個方法,這個方法將返回一個由棧軌跡中的元素所構成的數組,其中每個元素都表示棧中的一幀.元素0是棧頂元素,並且是調用序列中的最後一個方法調用(這個異常被建立和拋出之處);他有幾個不同的重載版本,可以將資訊輸出到不同的流中去.下面的代碼顯示了如何列印基本的異常資訊:
package lkl;///測試異常類Exception的常用方法public class ExceptionMethods { public static void main(String[] args){ try{ throw new Exception("My Exception"); }///通過捕獲異常類的基類Exception就可以捕獲所有類型的異常 catch(Exception e){ System.out.println("Caught Exception"); System.out.println("getMessage(): "+e.getMessage()); System.out.println("getLocalizedMessage(): "+e.getLocalizedMessage()); System.out.println("toString(): "+e.toString()); System.out.println("printStackTrace(): "); e.printStackTrace(System.out); } }}
三.重新拋出異常與異常鏈
有時候需要將捕獲的異常重新拋出,這樣在文法上是很簡單的.但問題是如果直接拋出的話,這個異常對象中資訊將仍然是原來異常對象中的資訊.如果我們想要跟新一下這個資訊,我們可以調用fillInStackTrace()方法,這個方法返回一個Throwable對象,它是通過將當前調用棧資訊填入原來那個對象而得到的.調用fillStackTrace()的那一行就成了異常的新的發生地.如果我們在捕獲了異常之後拋出了另外一種異常,那麼原來異常發生點的資訊會丟失,得到與fillInStackTrace()一樣的方法.
所謂的異常鏈指的是在捕獲一個異常後拋出另一個異常,並且希望把原始異常的資訊給保留下來.java中的Throwable子類在構造器中都可以接受一個cause(因由)對象作為參數.這個參數其實就是原先的異常對象,這樣通過把原始異常傳遞給新的異常,使得即使在當前位置建立並拋出了新的異常,也可以通過異常鏈追蹤到異常最初發生的位置.但是在Throwable的子類中,只對Error,Exception,RuntimeException這三個子類提供了帶cause因子的構造器,如果要把其它類型的異常連結起來,就應該使用initCause()方法而不是構造器了.
四.使用finally進行清理
引入finally語句的原因是我們希望一些代碼總是能得到執行,無論try塊中是否拋出異常.這樣異常處理的基本格式變成了下面這樣:
try{ ///可能會拋出異常的代碼 } catch(Type1 id1){ //處理Type1類型異常的代碼 } catch(Type2 id2){ ///處理type2類型異常的代碼 } finally{ ///總是會執行的代碼 }
其實catch和finally語句也可以只有一個出現.下面的代碼顯示了finally語句總能運行
package lkl;public class FinallyTest extends Exception{ public static int count=0; public static void main(String[] args){ while(true){ try{ //無論是不是拋出異常,finally語句都會執行 if(count++==0){ throw new MyException(); } System.out.println("No exception"); } catch(MyException me){ System.out.println("MyException"); } finally{ System.out.println("In finally clause"); if(count==2) break; } } }}
下面這份代碼說明就算在try中有return語句,finally語句仍會得到執行,看起來就像有多個返回點.
package lkl;public class MultipleReturns { public static void f(int i){ System.out.println("Initialization that requires cleanup"); try{///在函數reutrn之前,finally回先執行 System.out.println("Point 1"); if(i==1) return ; System.out.println("Point 2"); if(i==2) return ; System.out.println("Point 3"); if(i==3) return ; System.out.println("Point 4"); if(i==4) return; } ///在上面的return之前,會先執行finally語句 finally{ System.out.println("Perferming cleanup"); } } public static void main(String[] args){ for(int i=1;i<=4;i++) f(i); }}
那麼總是會執行的finally語句用來幹嘛?在java中我們主要用來清理除記憶體外的其它一些資源,這些資源套件括:已經開啟的檔案或網路連接,在螢幕上畫的映像等.
五.異常處理與繼承
最後一個問題是關於異常處理和繼承的.當異常處理和繼承一起發生的時候,我們需要關注的問題就是父類構造器拋出異常和子類構造器拋出異常的關係,在子類中重寫父類方法是怎樣聲明異常等問題.總結一下其實也就是二個規則:
1).子類重寫基類方法拋出的異常必須不比原基類方法拋出的異常類型更大.
2).雖然在文法上對子類構造器的異常聲明沒有規定,但是子類構造器的異常說明必須包括基類構造器的異常說明.(因為基類的構造器總是會被調用).
下面的代碼對此進行了示範:
public class Foul extends BaseballException{}public class PopFoul extends Foul{}public class BaseballException extends Exception{}public class Strike extends BaseballException{}public class RainedOut extends StormException{}public interface Storm { public void event() throws RainedOut; public void rainHard() throws RainedOut;}public abstract class Inning { public Inning() throws BaseballException{} ///定義拋出異常,但是實際上並沒有 public void event() throws BaseballException{}; //聲明拋出兩個異常 public abstract void atBat() throws Strike ,Foul; public void walk() {}}public class StormyInning extends Inning implements Storm{ public StormyInning() throws BaseballException,RainedOut{} public StormyInning(String s) throws Foul,BaseballException{} //因為基類方法中沒有拋出異常而子類覆蓋後的方法拋出了 ///所以編譯報錯 //void walk() throws PopFoul{} //介面不能給基類方法添加異常類型 //public void event() throws RainedOut{} ///重寫的方法拋出和基類方法一樣的異常是可以的 public void rainHard() throws RainedOut{} //基類的方法拋出了異常,而重寫的方法不拋出也是可以的 public void event() {} //重寫的方法拋出基類異常的子類也是可以的 public void atBat() throws PopFoul{} public static void main(String[] args) { try{///下面的兩句代碼中構造器中可能拋出RainedOut,BaseballException異常 ///atBat()方法可能拋出PopFoul異常.所以需要處理三種異常 StormyInning si = new StormyInning(); si.atBat(); } catch(PopFoul e){ System.out.println("Pop Foul"); } catch(RainedOut e){ System.out.println("Rained Out"); } catch(BaseballException e){ System.out.println("Generic baseball"); } try{//這段代碼進行了向上轉型,所以處理上面的三種情況外 ///還必須處理基類atBat()方法拋出的Strike異常 Inning i = new StormyInning(); i.atBat(); } catch(Strike e){ System.out.println("Strike"); } catch(PopFoul e){ System.out.println("Pop Foul"); } catch(RainedOut e){ System.out.println("Rained Out"); } catch(BaseballException e){ System.out.println("Generic baseball"); } }}
Thinking in Java---異常處理機制