摘要:一旦異常被拋出,就表明錯誤已無法挽回,也不能回來繼續執行。這種在編譯時被強制檢查的異常稱為被檢查的異常。通過獲取原始異常。構造器對于在構造階段可能會拋出異常,并要求清理的類,最安全的做法是使用嵌套的子句。
點擊進入我的博客
Java異常處理的目的在于通過使用少于目前數量的代碼來簡化大型、可靠的程序的生成,并且通過這種方式可以使你更自信:你的應用中沒有未處理的錯誤。
12.1 概念異常機制使代碼的閱讀、編寫和調試工作更加井井有條。
12.2 基本異常異常情形:是指組織當前方法或作用域繼續執行的問題。
拋出異常:異常情形發生時,程序在當前環境無法獲得必要的信息來解決問題,不能繼續執行,這是只能從當前環境跳出,把問題提交給上一級環境。
在堆上new一個異常對象
當前執行路徑種植,并且從當前環境中彈出對異常對象的引用
異常處理機制接管程序,并開始尋找一個恰當的地方(異常處理程序)來執行程序。
異常是我們可以將每件事都當作一個事務來考慮,而異常可以看護著這些事務的底線
異常可以看作是一種內建的恢復系統,當程序的某部分失敗了,異常將“恢復”到程序中某個已知的穩定點。
異常最重要的方面就是如果發生問題,不允許程序沿著其正常的路徑繼續走下去。
12.2.1 異常參數所有標準異常類都有兩個構造器:一個是默認構造器;另一個是接受字符串(錯誤信息)作為參數。
12.3 捕獲異常監控區域:一段可能產生異常的代碼,并且后面跟著處理這些異常的代碼。
12.3.1 try塊把所有可能產生異常的動作放到try塊中,然后在一個地方就可以捕獲所有異常。
12.3.2 異常處理程序拋出的異常必須在某處得到處理,這個地點就是異常處理程序,以緊跟在try塊之后的catch塊表示。
catch塊可以有多個,當異常被拋出時,異常處理程序只會處理第一個匹配的拋出異常,然后不會再執行剩下的語句。
Java支持終止模型,這這種模型中,假設錯誤非常關鍵,以至于程序無法返回到異常發生的地方繼續執行。一旦異常被拋出,就表明錯誤已無法挽回,也不能回來繼續執行。
另一種成為恢復模型,意思是異常處理程序的工作是執行錯誤,然后重新嘗試調用出問題的方法,并認為第二次可以成功。回復模型不實用的主要原因是它所導致的耦合:恢復性的處理程序需要了解異常拋出的地點,這勢必要包含依賴于拋出位置的非通用性代碼。
12.4 創建自定義異常Java提供的異常體系不可能預見所有的希望加以報告的錯誤,所以可以自己定義異常類。創建自定義異常類,必須從已有的異常類繼承,最好是選擇意思相近的異常類繼承。
通過System.err可以將錯誤發送給標準錯誤流,這通常比把信息輸出到System.out要好,因為System.out也許會被重定向,而System.err不會。e.printStackTrace()也是把信息發送給System.err。
12.4.1 異常與記錄日志class LoggingException extends Exception { private static final Logger LOGGER = Logger.getLogger("LoggingException"); public LoggingException() { // StringWriter writer = new StringWriter(); // printStackTrace(new PrintWriter(writer)); LOGGER.severe(this.toString()); } }
如上所示:可以把異常的信息打印到日志java.util.logging中,默認的日志輸出是System.err,也可以配置為文件等。
public class Test { private static final Logger LOGGER = Logger.getLogger("Test"); static void logException(Exception e) { // StringWriter writer = new StringWriter(); // printStackTrace(new PrintWriter(writer)); LOGGER.severe(e.toString()); } public static void main(String[] args) { try { throw new RuntimeException(); } catch (Exception e) { logException(e); } } }
如上所示:一般來說,在自定義的異常類(以及其他人的異常類)中不會耦合日志系統的信息,我們需要捕獲異常然后輸出異常信息到日志系統,所以需要在異常處理程序中產生日志消息。
一般來說,異常最重要的信息就是拋出的異常類本身,其他的功能基本上不用去管。
可以在方法上用throws關鍵字主動聲明該方法會拋出哪些異常,來告訴調用此方法的程序員去處理這些異常。這種在編譯時被強制檢查的異常稱為被檢查的異常。
void f() throws Exception { }
即使沒有throws并不表示此方法不會拋出異常。
如果方法中產生了異常卻沒有處理,編譯器會強制你要么處理這個異常,要么就主動聲明拋出這種異常。
可以聲明拋出異常,實際上卻不拋出。這樣的好處是為異常先占個位子,在定義抽象類和接口的時候尤為重要,這樣派生類或接口就能拋出這些預先聲明的異常。
12.6 捕獲所有異常因為Exception是所有異常的基類,所以通過catch(Exception e)可以捕獲所有異常。
盡量捕獲子類的異常,這樣可以攜帶更加細節的信息,最好把catch(Exception e)放在處理程序的末尾,防止它在其他處理程序之前先把異常捕獲了。
printStackTrace()方法所提供的信息可以通過getStackTrace()方法來直接訪問,這個方法返回由棧軌跡中的所有元素構成的數組,其中每一個元素都表示棧中的一幀。
數組中第0個元素是棧頂元素,并且是調用序列中的最后一個方法調用,并且數組中元素下標按照調用過程逆序排列。
數組中每個元素StackTraceElement,由類名、方法名、文件名、第幾行組成。
12.6.2 重新拋出異常可以把捕獲的異常在catch塊中向上一級環境中拋出,此時同一個try塊中其他catch塊將會被忽略。
調用e.fillInStackTrace()可以返回一個Throwable對象,它是通過把當前的調用棧信息填入原來的異常對象,此時該行將成為異常新的發生地,之前的異常在printStackTrace()方法中將不會打印(但沒有丟失)。
捕獲原來的異常之后可以拋出另一個新的異常,效果類似與e.fillInStackTrace(),不同的是有關原來異常發生點的信息會被丟失,只剩下新的異常拋出點。
12.6.3 異常鏈捕獲原來的異常之后可以拋出另一個新的異常,并且希望把原始異常的信息保存下來,這就是異常鏈。
Throwable的子類在構造器中接受一個cause對象作為參數,這個cause就表示原始異常,此時就可以在拋出新異常的同時追蹤到之前的異常。
要注意的是,Throwable的子類并不一定有這個構造器,此時你可以用initCause()方法。
通過e.getCause()獲取原始異常。
public class Test { public static void main(String[] args) throws Exception { try { g(); } catch (Exception e) { e.printStackTrace(); } } private static void f() throws Exception { throw new IndexOutOfBoundsException(); } private static void g() throws Exception { try { f(); } catch (Exception e) { // throw new RuntimeException(e); RuntimeException ee = new RuntimeException(); ee.initCause(e); throw ee; } } } // Output: java.lang.RuntimeException: java.lang.IndexOutOfBoundsException at s2.Test.g(Test.java:26) at s2.Test.main(Test.java:11) Caused by: java.lang.IndexOutOfBoundsException at s2.Test.f(Test.java:18) at s2.Test.g(Test.java:23) ... 1 more12.7 Java標準異常
Throwable這個類被用來表示任何可以作為異常拋出的類。它分為兩種類型:
Error:表示編譯時和系統錯誤,除特殊情況外,我們不需要理會此異常。
Exception:可以被拋出的異常,在JAVA類庫、用戶方法及運行時故障中都可能拋出的異常,我們通常關心此異常。
12.7.1 RuntimeException運行時異常(也稱為不受檢查異常)會被JVM自動拋出,所以不需要異常說明中把它們列出來。
無法預料的錯誤,比如從控制范圍外傳遞來的null引用。
程序員應該在代碼中檢查及避免的錯誤。
在一個地方發生的異常,常常會在另一個地方發生錯誤。
雖然它被設計用來處理一些煩人的運行時錯誤,這些錯誤往往是由代碼控制范圍外的不確定因素導致的,但是它對于發現某些編譯器無法檢測到的編程錯誤也是很有幫助的。
12.8 使用finally進行清理無論異常是否被拋出,finally塊中的語句總會被執行到。
12.8.1 finally用來做什么對于沒有垃圾回收和析構函數自動調用機制的語言來說,finally非常重要。它能使程序員保證:無論try塊里發生了什么,內存總能得到釋放。
對Java來說,當要把除內存之外的資源恢復到它們的初始狀態時,就要用到finally子句。如已經打開對文件或網絡連接。
不管有沒有出現異常,finally塊中代碼都會執行;
當try和catch中有return時,finally仍然會執行;
finally是在return后面的表達式運算后執行的(此時并沒有返回運算后的值,而是先把要返回的值保存起來,不管finally中的代碼怎么樣,返回的值都不會改變,任然是之前保存的值),所以函數返回值是在finally執行前確定的;
finally中最好不要包含return,否則程序會提前退出,返回值不是try或catch中保存的返回值。
12.8.3 缺憾:異常丟失try { throw new IllegalAccessException(); } catch (Exception e) { // throw new IndexOutOfBoundsException(); return; }
如果在finally塊中重新拋出或直接return都會使原來的異常丟失。
12.9 異常的限制當覆蓋方法的時候,只能拋出在基類方法的異常說明中列出的那些異常(此處異常指檢查性異常)。因為如果子類拋出的異常>父類拋出的異常的話,在向上轉型的時候,就父類方法并沒有聲明子類拋出的異常,這樣就會忽略掉該異常。換句話說,在繼承和覆蓋的時候異常只能縮小不能擴大。
異常限制對構造器不起作用
子類構造器不能捕獲父類構造器的異常(因為調用父類構造器必須是第一行語句)。
12.10 構造器對于在構造階段可能會拋出異常,并要求清理的類,最安全的做法是使用嵌套的try子句。
public class Test { public static void main(String[] args) { try { A a = new A(); try { a.func(); } finally { a.dispose(); } } catch (Exception e) { System.out.println(); } } } class A { public A() throws IOException { } public void func() {} // 清理該對象相關資源 public void dispose() {} }
雖然嵌套的try子句是合法的,但是嵌套的try語句并不是一種很優雅的編碼方式。Java7中新增了可以在try()自動關閉流的寫法。
在創建需要清理的對象之后,立即進入一個try-finally語句塊。
finally塊中依然有可能拋出異常,所以你可能需要額外的try-finally代碼塊。
12.11 異常匹配拋出異常的時候,異常處理系統會按照代碼的書寫順序找出“最近”的處理程序。找到匹配的處理程序之后,它就認為異常將得到處理,然后就不再繼續查找。
查找的時候并不要求拋出的異常同處理程序所聲明的異常完全匹配,派生類的對象也可以匹配其基類的處理程序。
異常處理的一個重要原則:只有在你知道如何處理的情況下才捕獲異常。
異常處理的一個重要目標:就是把錯誤處理的代碼同錯誤發生的地點相分離。
“被檢查的異常”使得問題變得有些復雜,因為你可能還沒準備好處理錯誤的時候,就被迫加上了try-catch語句,這時如果吞掉異常,將會產生嚴重的問題。
12.12.1 歷史:略 12.12.2 觀點:略 12.12.3 把異常傳遞給控制臺最簡單而不用寫多少代碼就能保護異常信息的方法,就是把它們傳遞給控制臺(及日志文件等)。
12.12.4 把“被檢查的異常”轉換為“不檢查的異常”try{ //…to do something useful } catch(IDontKnowWahtToDoWithThisCheckException e){ throw new RuntimeException(e); }
把“被檢查的異常”轉換為“不檢查的異常”:如果想把“被檢査的異常”這種功能“屏蔽”掉的話,這看上去像是一個好辦法。不用“吞下”異常,也不必把它放到方法的異常說明里面,而異常鏈還能保證你不會丟失任何原始異常的信息 。
繼續向上拋出異常:你可以不寫try-catch子句或異常說明,直接忽略異常,讓它自己沿著調用棧往上“冒泡”。
在恰當的級別處理問題。(在知道該如何處理的情況下才捕獲異常)
解決問題并且重新調用產生異常的方法。
進行少許修補,然后繞過異常發生的地方繼續執行。
用別的數據進行計算,以代替方法預計會返回的值。
把當前運行環境下能做的事情盡量做完,然后把相同的異常重拋到更高層。
把當前運行壞境下能做的事情盡量做完,然后把不同的異常拋到更高層。
終止程序。
進行簡化。(如果你的異常模式使問題變得太復雜,那用起來會非常痛著也很煩人)
讓類庫和程序更安全。(這既是在為調試做短期投資,也是在為程序的健壯做長期投資)
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/72194.html
摘要:而面向對象則是向程序員提供表示問題空間中元素的工具,我們將問題空間中的元素及其在解空間中的表示稱為對象。為什么要把對象看作是服務提供者呢這是將問題分解為對象集合的一種合理方式。職能太多,可能會導致對象的內聚性降低。在試圖將子類對象當作其基類 計算機是頭腦延伸的工具,是一種不同類型的表達媒體。本文以背景性的和補充性的材料,介紹包括開發方法概述在內的面向對象程序設計(Object-orie...
摘要:迭代器通常被成為輕量級對象創建它的代價很小。與迭代器可以用于數組和所有對象,之所以能夠工作,是因為繼承了接口。 點擊進入我的博客 我覺得本章名字改成容器似乎更好理解,持有對象讓人感到一頭霧水我們需要在任意時刻和任意位置創建任意數量的對象,所以依靠創建命名的引用來持有對象已經滿足不了需求。Java可以用數組和其他容器類來(List、Set、Queue、Map)來解決這個問題,不同的容器...
摘要:類最基本的作用,在于通過類獲取到相應的對象,在向對象發送消息時以期望對象做某些特定的事情。先導概念引用中一切皆對象,因此采用一個指向對象的引用來操縱對象。對象可以存活于作用域之外。 歡迎各位讀者關注我的微信公眾號,共同探討Java相關技術。生命不止,學習不休! showImg(https://segmentfault.com/img/bVboaBO?w=129&h=129); 也許你慢...
摘要:自動拆箱用賦值運算符把一個包裝類賦值給一個基本類型變量,或者是在包裝類進行數值運算時。指數計數,表示的冪按位操作符可以把值看成單比特值對待,的操作相同,但是不能用于布爾值。移位操作符高位包括符號位舍棄,低位補零。 點擊進入我的博客 3.1更簡單的打印語句 System.out.println(imbug); 通過編寫一個小類庫,并通過import static該方法來實現簡化打印(基...
摘要:前言編程思想這本書,陸陸續續讀了年,終于基本都瀏覽了一遍。每個對象對外暴露接口,程序通過對象暴露的接口向對象發送消息,獲取該對象的服務能力。異常處理異常處理,為編寫程序階段提供了一種預見性的防止程序崩潰的出路。 前言 《Java編程思想》這本書,陸陸續續讀了1年,終于基本都瀏覽了一遍。通過這本書,試圖理解作者的想法,才真的體會到Java思想。感謝本書的作者,不僅講述了java的語法,更...
閱讀 2226·2023-04-26 01:57
閱讀 3253·2023-04-25 16:30
閱讀 2330·2021-11-17 09:38
閱讀 1078·2021-10-08 10:14
閱讀 1388·2021-09-23 11:21
閱讀 3686·2019-08-29 17:28
閱讀 3457·2019-08-29 15:27
閱讀 950·2019-08-29 13:04