如今你已經知道了什麼是異常以及如何使用它們,如今是時候瞭解在程序中使用異常的優點了。java
異常提供了從程序的主邏輯中分離異常發生時應該作什麼的細節的方法,在傳統的編程中,錯誤檢測、報告和處理一般會致使混亂的意大利麪代碼,例如,考慮這裏的僞代碼方法將整個文件讀入內存。程序員
readFile { open the file; determine its size; allocate that much memory; read the file into memory; close the file; }
乍一看,這個功能彷佛很簡單,但它忽略瞭如下全部潛在的錯誤。編程
要處理此類狀況,readFile
函數必須具備更多代碼才能執行錯誤檢測、報告和處理,下面是一個函數的例子。segmentfault
errorCodeType readFile { initialize errorCode = 0; open the file; if (theFileIsOpen) { determine the length of the file; if (gotTheFileLength) { allocate that much memory; if (gotEnoughMemory) { read the file into memory; if (readFailed) { errorCode = -1; } } else { errorCode = -2; } } else { errorCode = -3; } close the file; if (theFileDidntClose && errorCode == 0) { errorCode = -4; } else { errorCode = errorCode and -4; } } else { errorCode = -5; } return errorCode; }
這裏有不少錯誤檢測、報告和返回,原始的七行代碼在雜亂中丟失了,更糟糕的是,代碼的邏輯流程也已丟失,所以很難判斷代碼是否正在作正確的事情:若是函數沒法分配足夠的內存,文件是否真的被關閉?在編寫方法三個月後修改方法時,確保代碼繼續作正確的事情變得更加困難,許多程序員經過忽略它來解決這個問題 — 當程序崩潰時會報告錯誤。函數
異常使你可以編寫代碼的主流程,並在其餘地方處理異常狀況,若是readFile
函數使用異常而不是傳統的錯誤管理技術,那麼它看起來更像是如下內容。code
readFile { try { open the file; determine its size; allocate that much memory; read the file into memory; close the file; } catch (fileOpenFailed) { doSomething; } catch (sizeDeterminationFailed) { doSomething; } catch (memoryAllocationFailed) { doSomething; } catch (readFailed) { doSomething; } catch (fileCloseFailed) { doSomething; } }
請注意,異常不會使你無需執行檢測、報告和處理錯誤的工做,但它們確實能夠幫助你更有效地組織工做。對象
異常的第二個優勢是可以在方法的調用堆棧中傳播錯誤報告,假設readFile
方法是主程序進行的一系列嵌套方法調用中的第四個方法:method1
調用method2
,它調用method3
,最後調用readFile
。內存
method1 { call method2; } method2 { call method3; } method3 { call readFile; }
假設method1
是惟一對readFile
中可能發生的錯誤感興趣的方法,傳統的錯誤通知技術強制method2
和method3
將readFile
返回的錯誤代碼傳播到調用堆棧,直到錯誤代碼最終到達method1
— 惟一感興趣的方法。資源
method1 { errorCodeType error; error = call method2; if (error) doErrorProcessing; else proceed; } errorCodeType method2 { errorCodeType error; error = call method3; if (error) return error; else proceed; } errorCodeType method3 { errorCodeType error; error = call readFile; if (error) return error; else proceed; }
回想一下,Java運行時環境在調用堆棧中向後搜索,以查找對處理特定異常感興趣的任何方法,一個方法能夠避開在其中拋出的任何異常,從而容許調用堆棧上更遠的方法捕獲它,所以,只有關心錯誤的方法才擔憂檢測錯誤。get
method1 { try { call method2; } catch (exception e) { doErrorProcessing; } } method2 throws exception { call method3; } method3 throws exception { call readFile; }
可是,正如僞代碼所示,避開異常須要中間方法的一些做用,必須在其throws
子句中指定能夠在方法中拋出的任何已檢查異常。
由於在程序中拋出的全部異常都是對象,因此異常的分組或分類是類層次結構的天然結果,Java平臺中的一組相關異常類的示例是在java.io
中定義的 — IOException
及其後代。IOException
是最多見的,表示執行I/O
時可能發生的任何類型的錯誤,它的後表明示更具體的錯誤,例如,FileNotFoundException
意味着文件沒在磁盤上。
方法能夠編寫能夠處理很是特定異常的特定處理程序,FileNotFoundException
類沒有後代,所以如下處理程序只能處理一種類型的異常。
catch (FileNotFoundException e) { ... }
方法能夠經過在catch
語句中指定任何異常的超類來基於其組或常規類型捕獲異常,例如,要捕獲全部I/O異常,不管其具體類型如何,異常處理程序都會指定IOException
參數。
catch (IOException e) { ... }
此處理程序將可以捕獲全部I/O異常,包括FileNotFoundException
、EOFException
等,你能夠經過查詢傳遞給異常處理程序的參數來查找有關所發生狀況的詳細信息,例如,使用如下命令打印堆棧跟蹤。
catch (IOException e) { // Output goes to System.err. e.printStackTrace(); // Send trace to stdout. e.printStackTrace(System.out); }
你甚至能夠設置一個異常處理程序來處理任何Exception
。
// A (too) general exception handler catch (Exception e) { ... }
Exception
類接近Throwable
類層次結構的頂部,所以,除了處理程序要捕獲的那些異常以外,此處理程序還將捕獲許多其餘異常。若是你但願程序執行全部操做,你可能但願以這種方式處理異常,例如,爲用戶打印出錯誤消息而後退出。
可是,在大多數狀況下,你但願異常處理程序儘量具體,緣由是,處理程序必須作的第一件事是肯定發生了什麼類型的異常,而後才能決定最佳的恢復策略。實際上,經過不捕獲特定錯誤,處理程序必須適應任何可能性,過於通用的異常處理程序經過捕獲和處理程序員沒有預料到的異常,以及處理程序沒有打算處理的異常,可使代碼更容易出錯。
如上所述,你能夠建立異常組並以通常方式處理異常,或者你可使用特定的異常類型來區分異常並以精確的方式處理異常。
程序可使用異常來指示發生了錯誤,要拋出異常,請使用throw
語句併爲其提供異常對象 — Throwable
的後代 — 以提供有關發生的特定錯誤的信息,拋出未捕獲的已檢查異常的方法必須在其聲明中包含throws
子句。
程序能夠經過結合使用try
、catch
和finally
塊來捕獲異常。
try
塊標識可能發生異常的代碼塊。catch
塊標識一個代碼塊,稱爲異常處理程序,能夠處理特定類型的異常。finally
塊標識了一個保證執行的代碼塊,它是關閉文件、恢復資源以及在try
塊中包含代碼以後進行清理的正確位置。try
語句應包含至少一個catch
塊或finally
塊,而且可能有多個catch
塊。
異常對象的類指示拋出的異常類型,異常對象能夠包含有關錯誤的更多信息,包括錯誤消息,使用鏈式異常時,異常能夠指向致使異常的異常,異常又能夠指向致使它的異常,依此類推。