Java的基本概念是結構不佳的代碼不能運行餘下的問題必須在運行期間解決,這就須要錯誤源能經過某種方式,把適當的信息傳遞給某個接收者——該接收者將知道如何正確處理這裏問題。java
使用異常所帶來的另外一個至關明顯的好處,它每每可以下降錯誤處理代碼的複雜度。程序員
異常情形是指阻止當前方法或做用域繼續執行的問題。把異常情形與普通問題相區分很重要,普通問題是指,在當前環境下能獲得足夠的信息,總能處理這個錯誤。而對於異常情形,就不能繼續下去了,由於在當前環境下沒法得到必要的信息來解決問題。你所能作的就是從當前環境跳出,而且把問題提交給上一級環境。這就是拋出異常所發生的事情。
當拋出異常後,有幾件事就會發生。首先,同Java中其餘對象的建立同樣,將使用new在堆上建立異常對象,而後,當前的執行路徑被終止,而且從當前環境中彈出堆異常對象的引用。此時,異常處理機制接管程序,並開始尋找一個恰當的地方來繼承執行程序。這個恰當的地方就是異常處理程序,它的任務就是將程序從錯誤狀態中恢復,以使程序能要麼換一種方式運行,要麼繼承運行下去。編程
咱們總用new在堆上建立異常對象,這也伴隨着存儲控件的分配和構造器的調用。全部標準異常類都有兩個構造器:數組
使用new建立了異常對象以後,此對象的引用將傳給throw。
能夠簡單把異常處理當作一種不一樣的返回機制。拋出異常的方法從當前的做用域退出。將返回一個異常對象,而後退出方法做用域。安全
異常如何被捕獲,必須首先理解監控區域。app
異常處理機制,能夠把全部動做都放在try塊中,而後再一個地方處理捕獲的異常就能夠了。ide
拋出的異常必須在某處獲得處理。這個地點就是異常處理程序。異常處理程序緊跟在try塊後,以關鍵字catch表示。
當異常拋出時,異常處理機制將負責搜尋參數與異常類型相匹配的第一個處理程序。ui
異常處理理論有兩種基本模型:this
恢復模型會致使耦合:恢復性的處理程序須要瞭解異常拋出的地點,着勢必要包含依賴於拋出位置的非通用性代碼。這增長了代碼編寫和維護的困難。設計
要本身定義異常,必須從已有的異常類繼承,最好選擇意思相近的異常類繼承。
public class InheritingExceptions { public void f() throws SimpleException { System.out.println("Throw SimpleException from f()"); throw new SimpleException(); } public static void main(String[] args) { InheritingExceptions sed = new InheritingExceptions(); try { sed.f(); } catch (SimpleException e) { System.out.println("Caught it!"); } } } class SimpleException extends Exception { }
編譯器建立了默認構造器,它將自動調用基類的默認構造器。
public class FullConstructors { public static void f() throws MyException{ System.out.println("Throwing MyExcetion from f()"); throw new MyException(); } public static void g() throws MyException{ System.out.println("Throwing MyExcetion from g()"); throw new MyException("Originated in g()"); } public static void main(String[] args){ try{ f(); } catch (MyException e){ e.printStackTrace(System.out); } try{ g(); } catch (MyException e){ e.printStackTrace(System.out); } } } class MyException extends Exception{ public MyException(){} public MyException(String msg){super(msg);} }
使用基本日誌記錄功能
import java.io.PrintWriter; import java.io.StringWriter; import java.util.logging.Logger; public class LoggingExceptions { public static void main(String[] args){ try{ throw new LoggingException(); } catch (LoggingException e){ System.out.println("Caught "+e); } try{ throw new LoggingException(); } catch (LoggingException e){ System.out.println("Caught "+e); } } } class LoggingException extends Exception{ private static Logger logger= Logger.getLogger("LoggingException "); public LoggingException(){ StringWriter trace=new StringWriter(); printStackTrace(new PrintWriter(trace)); logger.severe(trace.toString()); } }
須要捕獲或記錄他人編寫的異常,必須在異常處理程序中生成日誌消息
import java.io.PrintWriter; import java.io.StringWriter; import java.util.logging.Logger; public class LoggingExceptions2 { private static Logger logger=Logger.getLogger("LoggingExceptions2"); static void logException(Exception e){ StringWriter trace=new StringWriter(); e.printStackTrace(new PrintWriter(trace)); logger.severe(trace.toString()); } public static void main(String[] args){ try{ throw new NullPointerException(); } catch(NullPointerException e){ logException(e); } } }
更進一步自定義異常,加入額外的構造器和成員
public class ExtraFeatures { public static void f() throws MyException2{ System.out.println("Throwing MyException2 from f()"); throw new MyException2(); } public static void g() throws MyException2{ System.out.println("Throwing MyException2 from g()"); throw new MyException2("Originated in g()"); } public static void h() throws MyException2{ System.out.println("Throwing MyException2 from h()"); throw new MyException2("Originated in g()",47); } public static void main(String[] args){ try{ f(); } catch (MyException2 e){ e.printStackTrace(System.out); } try{ g(); } catch (MyException2 e){ e.printStackTrace(System.out); } try{ h(); } catch (MyException2 e){ e.printStackTrace(System.out); System.out.println("e.val()="+e.val()); } } } class MyException2 extends Exception{ private int x; public MyException2(){} public MyException2(String msg){super(msg);} public MyException2(String msg,int x){super(msg);this.x=x;} public int val(){return x;} public String getMessage(){ return "Detail message:"+x+" "+super.getMessage(); } }
異常也是一種對象,全部能夠繼續修改異常類,得到更強大的功能。
Java提供語法,使能夠告知客戶端程序員某個方法可能會拋出的異常類型,而後客戶端程序員就能夠進行相應的處理。這就是異常說明,它屬於方法說明的一部分,緊跟在形式參數以後。
異常說明使用了附加的關鍵字throws,後面接一個潛在的異常類型的列表,因此方法定義可能看起來像這樣:
void f() throws TooBig,TooSmall { }
若是方法裏的代碼產生了異常卻沒有處理,編譯器會發現這個問題而且提醒你,要麼處理這個異常,要麼就在異常說明中代表此方法將產生異常。
這種編譯時被強制檢查的異常稱爲被檢查的異常。
gillInStackTrace()用於在Throwable對象的內部記錄棧幀的當前狀態。這在程序從新拋出錯誤或異常時頗有用。
printStackTrace()方法所提供的信息能夠經過getStackTrace()方法來直接訪問,這個方法將返回一個由棧軌跡中的元素所構成的數組,其中,每一個元素表示棧中的一貞。
public class WhoCalled { static void f(){ try{ throw new Exception(); } catch (Exception e){ for(StackTraceElement ste:e.getStackTrace()) System.out.println(ste.getMethodName()); } } static void g(){f();} static void h(){g();} public static void main(String[] args){ f(); System.out.println("--------------"); g(); System.out.println("--------------"); h(); } }
從新拋出異常會把異常拋給上一級環境中的異常處理程序,同一個try塊的後續catch子句將被忽略。異常對象的全部信息都得以保持,因此高一級環境中捕獲此異常的處理程序能夠從這個異常對象中獲得全部信息。
public class Rethrowing { public static void f() throws Exception{ System.out.println("originating the "); throw new Exception("thrown from f()"); } public static void g() throws Exception{ try{ f(); } catch (Exception e){ System.out.println("Inside g(),e.printStackTrace()"); e.printStackTrace(System.out); throw e; } } public static void h() throws Exception{ try{ f(); }catch (Exception e){ System.out.println("Inside h(),e.printStackTrace()"); e.printStackTrace(System.out); throw (Exception) e.fillInStackTrace(); } } public static void main(String[] args){ try{ g(); }catch (Exception e){ System.out.println("main: printStackTrace()"); e.printStackTrace(System.out); } try{ h(); }catch (Exception e){ System.out.println("main: printStackTrace()"); e.printStackTrace(System.out); } } }
有可能在捕獲異常以後拋出另外一種異常。這麼作的話,獲得的效果相似於使用fillInStackTrace(),有關原來異常發生點的信息會丟失,剩下的是於新的跑出點有關的信息:
public class RethrowNew { public static void f() throws OneException{ System.out.println("originating the exception"); throw new OneException("thrown from f()"); } public static void main(String[] args){ try{ try{ f(); }catch (OneException e){ System.out.println("Caught in inner try,e.printStackTrace"); e.printStackTrace(System.out); throw new TwoException("from inner try"); } }catch (TwoException e){ System.out.println("Caught in inner try,e.printStackTrace"); e.printStackTrace(System.out); } } } class OneException extends Exception{ public OneException(String s){super(s);} } class TwoException extends Exception{ public TwoException(String s){super(s);} }
異常都是用new在堆上建立的對象,全部垃圾回收器會自動把它們清理掉。
想在捕獲一個異常後拋出另外一個異常,而且但願把原始異常信息保持下來,這被稱爲異常鏈。如今全部Throwable的子類在構造器中均可以接受一個causr對象做爲參數。這個cause就用來表示原始異常,這樣經過把原始異常傳遞給新的異常,使得即便在當前位置建立並拋出了新的異常,也能經過這個異常鏈最終到最初發生的位置。
class DynamicFieldsException extends Exception {} public class DynamicFields { private Object[][] fields; public DynamicFields(int initialSize) { fields = new Object[initialSize][2]; for(int i = 0; i < initialSize; i++) fields[i] = new Object[] { null, null }; } public String toString() { StringBuilder result = new StringBuilder(); for(Object[] obj : fields) { result.append(obj[0]); result.append(": "); result.append(obj[1]); result.append("\n"); } return result.toString(); } private int hasField(String id) { for(int i = 0; i < fields.length; i++) if(id.equals(fields[i][0])) return i; return -1; } private int getFieldNumber(String id) throws NoSuchFieldException { int fieldNum = hasField(id); if(fieldNum == -1) throw new NoSuchFieldException(); return fieldNum; } private int makeField(String id) { for(int i = 0; i < fields.length; i++) if(fields[i][0] == null) { fields[i][0] = id; return i; } // No empty fields. Add one: Object[][] tmp = new Object[fields.length + 1][2]; for(int i = 0; i < fields.length; i++) tmp[i] = fields[i]; for(int i = fields.length; i < tmp.length; i++) tmp[i] = new Object[] { null, null }; fields = tmp; // Recursive call with expanded fields: return makeField(id); } public Object getField(String id) throws NoSuchFieldException { return fields[getFieldNumber(id)][1]; } public Object setField(String id, Object value) throws DynamicFieldsException { if(value == null) { // Most exceptions don't have a "cause" constructor. // In these cases you must use initCause(), // available in all Throwable subclasses. DynamicFieldsException dfe = new DynamicFieldsException(); dfe.initCause(new NullPointerException()); throw dfe; } int fieldNumber = hasField(id); if(fieldNumber == -1) fieldNumber = makeField(id); Object result = null; try { result = getField(id); // Get old value } catch(NoSuchFieldException e) { // Use constructor that takes "cause": throw new RuntimeException(e); } fields[fieldNumber][1] = value; return result; } public static void main(String[] args) { DynamicFields df = new DynamicFields(3); System.out.print(df); try { df.setField("d", "A value for d"); df.setField("number", 47); df.setField("number2", 48); System.out.print(df); df.setField("d", "A new value for d"); df.setField("number3", 11); System.out.print("df: " + df); System.out.print("df.getField(\"d\") : " + df.getField("d")); Object field = df.setField("d", null); // Exception } catch(NoSuchFieldException e) { e.printStackTrace(System.out); } catch(DynamicFieldsException e) { e.printStackTrace(System.out); } } }
Throwable這個Java類被用來表示任何能夠做爲異常拋出的類。Throwable對象可分爲兩中類型:Error用來表示編譯時和系統錯誤。Exception是能夠被拋出的基本類型。
RuntimeException類型的異常也許會穿越全部執行路徑直達main()方法,而不會被捕獲。
public class NeverCaught { static void f(){ throw new RuntimeException("From f()"); } static void g(){ f(); } public static void main(String[] args){ g(); } }
RuntimeException表明的是編程錯誤:
做爲程序員,應該在代碼中檢查的錯誤
應該把異常機制用來處理一些煩人的運行時錯誤,這些錯誤每每是由代碼控制能力以外的因素致使的。
public class FinallyWorks { static int count=0; public static void main(String[] args){ while(true){ try{ if(count++==0) throw new ThreeException(); System.out.println("No exception"); } catch (ThreeException e){ System.out.println("ThreeException"); } finally { System.out.println("finally cluse "+count); if(count==2)break; } } } } class ThreeException extends Exception{}
當要把除內存以外的資源恢復到初始狀態時,就要用到finally子句
public class WithFinally { static Switch sw = new Switch(); public static void main(String[] args) { try { sw.on(); // Code that can throw exceptions... OnOffSwitch.f(); } catch(OnOffException1 e) { System.out.println("OnOffException1"); } catch(OnOffException2 e) { System.out.println("OnOffException2"); } finally { sw.off(); } } } class Switch { private boolean state = false; public boolean read() { return state; } public void on() { state = true; System.out.print(this); } public void off() { state = false; System.out.print(this); } public String toString() { return state ? "on" : "off"; } } class OnOffSwitch { private static Switch sw = new Switch(); public static void f() throws OnOffException1,OnOffException2 {} public static void main(String[] args) { try { sw.on(); // Code that can throw exceptions... f(); sw.off(); } catch(OnOffException1 e) { System.out.println("OnOffException1"); sw.off(); } catch(OnOffException2 e) { System.out.println("OnOffException2"); sw.off(); } } } class OnOffException1 extends Exception {} class OnOffException2 extends Exception {}
當設計break和continue語句時,finally也會獲得執行
由於finally子句總會執行,因此在一個方法中,能夠從多個點返回,而且能夠保證重要的清理工做仍舊會執行:
public class MultipleReturns { public static void f(int i){ System.out.println("Initialization that requires"); try{ 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; } finally { System.out.println("end"); } } public static void main(String[] args){ for(int i=1;i<=3;i++) f(i); } }
public class LoseMessage { void f() throws VeryImportantException { throw new VeryImportantException(); } void dispose() throws HoHumException { throw new HoHumException(); } public static void main(String[] args) { try { LoseMessage lm = new LoseMessage(); try { lm.f(); } finally { lm.dispose(); } } catch(Exception e) { System.out.println(e); } } } class VeryImportantException extends Exception { public String toString() { return "A very important exception!"; } } class HoHumException extends Exception { public String toString() { return "A trivial exception"; } }
前一個異常尚未處理就拋出了下一個異常。
當覆蓋方法的時候,只能拋出在基類方法的異常說明裏列出的那些異常。
class BaseballException extends Exception {} class Foul extends BaseballException {} class Strike extends BaseballException {} abstract class Inning { public Inning() throws BaseballException {} public void event() throws BaseballException { // Doesn't actually have to throw anything } public abstract void atBat() throws Strike, Foul; public void walk() {} // Throws no checked exceptions } class StormException extends Exception {} class RainedOut extends StormException {} class PopFoul extends Foul {} interface Storm { public void event() throws RainedOut; public void rainHard() throws RainedOut; } public class StormyInning extends Inning implements Storm { // OK to add new exceptions for constructors, but you // must deal with the base constructor exceptions: public StormyInning() throws RainedOut, BaseballException {} public StormyInning(String s) throws Foul, BaseballException {} // Regular methods must conform to base class: //! void walk() throws PopFoul {} //Compile error // Interface CANNOT add exceptions to existing // methods from the base class: //! public void event() throws RainedOut {} // If the method doesn't already exist in the // base class, the exception is OK: public void rainHard() throws RainedOut {} // You can choose to not throw any exceptions, // even if the base version does: public void event() {} // Overridden methods can throw inherited exceptions: public void atBat() throws PopFoul {} public static void main(String[] args) { try { 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 exception"); } // Strike not thrown in derived version. try { // What happens if you upcast? Inning i = new StormyInning(); i.atBat(); // You must catch the exceptions from the // base-class version of the method: } catch(Strike e) { System.out.println("Strike"); } catch(Foul e) { System.out.println("Foul"); } catch(RainedOut e) { System.out.println("Rained out"); } catch(BaseballException e) { System.out.println("Generic baseball exception"); } } }
派生類構造器不能捕獲基類構造器拋出的異常。
派生類能夠不拋出任何異常,即便它是基類定義的異常。
若是處理的恰好是派生類對象的話,編譯器只會強制要求你捕獲這個類所拋出的異常,若是將它向上轉型成基類,那麼編譯器就會要求你捕獲基類的異常。
在繼承中,基類的方法必須出如今派生類裏,在繼承和覆蓋的過程當中,某個特定的方法異常說明接口不是變大而是變小——這剛好和類接口在繼承時的情形相反。
通常在構造器中,會把對象設置成安全的初始狀態。若是在構造器內拋出異常,這些清理行爲也許就不能正常工做了。就算使用finally每次都執行了清理代碼,若是構造器在器執行的中途異常,那麼某些對象就沒有被建立成功,而這些部分在finally子句中卻要被清理。
import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; public class InputFile { private BufferedReader in; public InputFile(String fname) throws Exception { try { in = new BufferedReader(new FileReader(fname)); // Other code that might throw exceptions } catch (FileNotFoundException e) { System.out.println("Could not open " + fname); // Wasn't open, so don't close it throw e; } catch (Exception e) { // All other exceptions must close it try { in.close(); } catch (IOException e2) { System.out.println("in.close() unsuccessful"); } throw e; // Rethrow } finally { // Don't close it here!!! } } public String getLine() { String s; try { s = in.readLine(); } catch (IOException e) { throw new RuntimeException("readLine() failed"); } return s; } public void dispose() { try { in.close(); System.out.println("dispose() successful"); } catch (IOException e2) { throw new RuntimeException("in.close() failed"); } } }
對於構造階段可能會拋出的異常,而且要求清理的類,最安全的時使用嵌套try子句:
public class Cleanup { public static void main(String[] args) { try { InputFile in = new InputFile("Cleanup.java"); try { String s; int i = 1; while ((s = in.getLine()) != null) ; // Perform line-by-line processing here... } catch (Exception e) { System.out.println("Caught Exception in main"); e.printStackTrace(System.out); } finally { in.dispose(); } } catch (Exception e) { System.out.println("InputFile construction failed"); } } }
對InputFile對象的構造在其本身的try語句塊中有效,若是構造失敗,將進入外部的catch子句,而dispose()方法不會被調用。若是構形成功,咱們確定要確保對象可以被清理,所以在構造器以後當即建立一個新的try語句。執行清理的finally與內部熱try語句塊相關聯。這種方式中,finally子句在構造失敗時不會執行的,而是構形成功時纔會被執行。
這種通用的清理慣用法在構造器不拋出任何異常時也應該運用。基本規則是:在建立須要清理的對象以後,當即進入一個try-finally語句中:
class NeedsCleanup { // Construction can't fail private static long counter = 1; private final long id = counter++; public void dispose() { System.out.println("NeedsCleanup " + id + " disposed"); } } class ConstructionException extends Exception { } class NeedsCleanup2 extends NeedsCleanup { // Construction can fail: public NeedsCleanup2() throws ConstructionException { } } public class CleanupIdiom { public static void main(String[] args) { // Section 1: NeedsCleanup nc1 = new NeedsCleanup(); try { // ... } finally { nc1.dispose(); } // Section 2: // If construction cannot fail you can group objects: NeedsCleanup nc2 = new NeedsCleanup(); NeedsCleanup nc3 = new NeedsCleanup(); try { // ... } finally { nc3.dispose(); // Reverse order of construction nc2.dispose(); } // Section 3: // If construction can fail you must guard each one: try { NeedsCleanup2 nc4 = new NeedsCleanup2(); try { NeedsCleanup2 nc5 = new NeedsCleanup2(); try { // ... } finally { nc5.dispose(); } } catch (ConstructionException e) { // nc5 constructor System.out.println(e); } finally { nc4.dispose(); } } catch (ConstructionException e) { // nc4 constructor System.out.println(e); } } }
Section1至關簡單:遵循了在可去除對象以後緊跟try—finally原則。若是對象構造不能失敗,就不須要任何catch。在Section2中,爲了構造和清理,能夠看到具備不能失敗的構造器對象能夠羣組在一塊兒。
Section3展現瞭如何處理那些具備能夠失敗的構造器,且須要清理的對象。
拋出異常的時候,異常處理系統會按照代碼的書寫順序找出最近的處理程序。找到匹配的處理程序以後,它就認爲異常將獲得處理,而後就不在繼續查找。
若是把捕獲基類的catch子句放在最前面,以此想把派生類的異常全屏蔽,就像這樣:
try { throw new Sneeze(); } catch(Annoance a){} catch(Sneeze s){}
這樣編譯器就會發現Sneeze的catch子句永遠也得不到執行,所以它會向你報錯。
異常處理系統就像一個活門,使你能放棄程序的正常執行序列。開發異常處理的初衷是爲了方便處理錯誤。
異常處理的一個重要原則:只有在你知道如何處理的狀況下才捕獲異常。就是把錯誤的代碼同錯誤發生地點相分離。
被檢查的異常:由於它們強制你在可能還沒準備好處理錯誤的時候被迫加上catch子句,這就致使了吞食則有害的問題。
應在下列狀況下使用異常: