承接上篇博文:java提高篇-----異常(一)
五、自定義異常
Java確實給我們提供了非常多的異常,但是異常體系是不可能預見所有的希望加以報告的錯誤,所以Java允許我們自定義異常來表現程序中可能會遇到的特定問題,總之就是一句話:我們不必拘泥於Java中已有的異常類型。
Java自定義異常的使用要經歷如下四個步驟:
1、定義一個類繼承Throwable或其子類。
2、添加構造方法(當然也可以不用添加,使用默認構造方法)。
3、在某個方法類拋出該異常。
4、捕捉該異常。
/** 自定義異常 繼承Exception類 **/ public class MyException extends Exception{ public MyException(){ } public MyException(String message){ super(message); } } public class Test { public void display(int i) throws MyException{ if(i == 0){ throw new MyException("該值不能為0......."); } else{ System.out.println( i / 2); } } public static void main(String[] args) { Test test = new Test(); try { test.display(0); System.out.println("---------------------"); } catch (MyException e) { e.printStackTrace(); } } }
運行結果:
六、異常鏈
在設計模式中有一個叫做責任鏈模式,該模式是將多個對象鏈接成一條鏈,客戶端的請求沿着這條鏈傳遞直到被接收、處理。同樣Java異常機制也提供了這樣一條鏈:異常鏈。
我們知道每遇到一個異常信息,我們都需要進行try…catch,一個還好,如果出現多個異常呢?分類處理肯定會比較麻煩,那就一個Exception解決所有的異常吧。這樣確實是可以,但是這樣處理勢必會導致后面的維護難度增加。最好的辦法就是將這些異常信息封裝,然后捕獲我們的封裝類即可。
誠然在應用程序中,我們有時候不僅僅只需要封裝異常,更需要傳遞。怎么傳遞?throws!!binge,正確!!但是如果僅僅只用throws拋出異常,那么你的封裝類,怎么辦??
我們有兩種方式處理異常,一是throws拋出交給上級處理,二是try…catch做具體處理。但是這個與上面有什么關聯呢?try…catch的catch塊我們可以不需要做任何處理,僅僅只用throw這個關鍵字將我們封裝異常信息主動拋出來。然后在通過關鍵字throws繼續拋出該方法異常。它的上層也可以做這樣的處理,以此類推就會產生一條由異常構成的異常鏈。
通過使用異常鏈,我們可以提高代碼的可理解性、系統的可維護性和友好性。
同理,我們有時候在捕獲一個異常后拋出另一個異常信息,並且希望將原始的異常信息也保持起來,這個時候也需要使用異常鏈。
在異常鏈的使用中,throw拋出的是一個新的異常信息,這樣勢必會導致原有的異常信息丟失,如何保持?在Throwable及其子類中的構造器中都可以接受一個cause參數,該參數保存了原有的異常信息,通過getCause()就可以獲取該原始異常信息。
語法:
public void test() throws XxxException{ try { //do something:可能拋出異常信息的代碼塊 } catch (Exception e) { throw new XxxException(e); } }
示例:
public class Test { public void f() throws MyException{ try { FileReader reader = new FileReader("G:\\myfile\\struts.txt"); Scanner in = new Scanner(reader); System.out.println(in.next()); } catch (FileNotFoundException e) { //e 保存異常信息 throw new MyException("文件沒有找到--01",e); } } public void g() throws MyException{ try { f(); } catch (MyException e) { //e 保存異常信息 throw new MyException("文件沒有找到--02",e); } } public static void main(String[] args) { Test t = new Test(); try { t.g(); } catch (MyException e) { e.printStackTrace(); } } }
運行結果:
com.test9.MyException: 文件沒有找到--02 at com.test9.Test.g(Test.java:31) at com.test9.Test.main(Test.java:38) Caused by: com.test9.MyException: 文件沒有找到--01 at com.test9.Test.f(Test.java:22) at com.test9.Test.g(Test.java:28) ... 1 more Caused by: java.io.FileNotFoundException: G:\myfile\struts.txt (系統找不到指定的路徑。) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(FileInputStream.java:106) at java.io.FileInputStream.<init>(FileInputStream.java:66) at java.io.FileReader.<init>(FileReader.java:41) at com.test9.Test.f(Test.java:17) ... 2 more
如果在程序中,去掉e,也就是:throw new MyException("文件沒有找到--02");
那么異常信息就保存不了,運行結果如下:
com.test9.MyException: 文件沒有找到--02 at com.test9.Test.g(Test.java:31) at com.test9.Test.main(Test.java:38)
PS:其實對於異常鏈鄙人使用的也不是很多,理解的不是很清楚,望各位指正!!!!
七、異常的使用誤區
首先我們先看如下示例:該實例能夠反映java異常的不正確使用(其實這也是我剛剛學Java時寫的代碼)!!
OutputStreamWriter out = null; java.sql.Connection conn = null; try { // ---------1 Statement stat = conn.createStatement(); ResultSet rs = stat.executeQuery("select *from user"); while (rs.next()){ out.println("name:" + rs.getString("name") + "sex:" + rs.getString("sex")); } conn.close(); //------2 out.close(); } catch (Exception ex){ //------3 ex.printStackTrace(); //------4 }
1、-----------1
對於這個try…catch塊,我想他的真正目的是捕獲SQL的異常,但是這個try塊是不是包含了太多的信息了。這是我們為了偷懶而養成的代碼壞習慣。有些人喜歡將一大塊的代碼全部包含在一個try塊里面,因為這樣省事,反正有異常它就會拋出,而不願意花時間來分析這個大代碼塊有那幾塊會產生異常,產生什么類型的異常,反正就是一簍子全部搞定。這就想我們出去旅游將所有的東西全部裝進一個箱子里面,而不是分類來裝,雖不知裝進去容易,找出來難啊!!!所有對於一個異常塊,我們應該仔細分清楚每塊的拋出異常,因為一個大代碼塊有太多的地方會出現異常了。
結論一:盡可能的減小try塊!!!
2、--------2
在這里你發現了什么?異常改變了運行流程!!不錯就是異常改變了程序運行流程。如果該程序發生了異常那么conn.close(); out.close();是不可能執行得到的,這樣勢必會導致資源不能釋放掉。所以如果程序用到了文件、Socket、JDBC連接之類的資源,即使遇到了異常,我們也要確保能夠正確釋放占用的資源。這里finally就有用武之地了:不管是否出現了異常,finally總是有機會運行的,所以finally用於釋放資源是再適合不過了。
結論二:保證所有資源都被正確釋放。充分運用finally關鍵詞。
3、----------3
對於這個代碼我想大部分人都是這樣處理的,(LZ也是)。使用這樣代碼的人都有這樣一個心理,一個catch解決所有異常,這樣是可以,但是不推薦!為什么!首先我們需要明白catch塊所表示是它預期會出現何種異常,並且需要做何種處理,而使用Exception就表示他要處理所有的異常信息,但是這樣做有什么意義呢?
這里我們再來看看上面的程序實例,很顯然它可能需要拋出兩個異常信息,SQLException和IOException。所以一個catch處理兩個截然不同的Exception明顯的不合適。如果用兩個catch,一個處理SQLException、一個處理IOException就好多了。所以:
結論三:catch語句應當盡量指定具體的異常類型,而不應該指定涵蓋范圍太廣的Exception類。 不要一個Exception試圖處理所有可能出現的異常。
4、----------4
這個就問題多多了,我敢保證幾乎所有的人都這么使用過。這里涉及到了兩個問題,一是,捕獲了異常不做處理,二是異常信息不夠明確。
4.1、捕獲異常不做處理,就是我們所謂的丟棄異常。我們都知道異常意味着程序出現了不可預期的問題,程序它希望我們能夠做出處理來拯救它,但是你呢?一句ex.printStackTrace()搞定,這是多么的不負責任對程序的異常情況不理不顧。雖然這樣在調試可能會有一定的幫助,但是調試階段結束后呢?不是一句ex.printStackTrace()就可以搞定所有的事情的!
那么怎么改進呢?有四種選擇:
1、處理異常。對所發生的的異常進行一番處理,如修正錯誤、提醒。再次申明ex.printStackTrace()算不上已經“處理好了異常”.
2、重新拋出異常。既然你認為你沒有能力處理該異常,那么你就盡情向上拋吧!!!
3、封裝異常。這是LZ認為最好的處理方法,對異常信息進行分類,然后進行封裝處理。
4、不要捕獲異常。
4.2、異常信息不明確。我想對於這樣的:java.io.FileNotFoundException: ………信息除了我們IT人沒有幾個人看得懂和想看吧!所以在出現異常后,我們最好能夠提供一些文字信息,例如當前正在執行的類、方法和其他狀態信息,包括以一種更適合閱讀的方式整理和組織printStackTrace提供的信息。起碼我公司是需要將異常信息所在的類、方法、何種異常都需要記錄在日志文件中的。
所以:
結論四:既然捕獲了異常,就要對它進行適當的處理。不要捕獲異常之后又把它丟棄,不予理睬。 不要做一個不負責的人。
結論五:在異常處理模塊中提供適量的錯誤原因信息,組織錯誤信息使其易於理解和閱讀。
對於異常還有以下幾個注意地方:
六、不要在finally塊中處理返回值。
七、不要在構造函數中拋出異常。
八、try…catch、throw、throws
在這里主要是區分throw和throws。
throws是方法拋出異常。在方法聲明中,如果添加了throws子句,表示該方法即將拋出異常,異常的處理交由它的調用者,至於調用者任何處理則不是它的責任范圍內的了。所以如果一個方法會有異常發生時,但是又不想處理或者沒有能力處理,就使用throws吧!
而throw是語句拋出異常。它不可以單獨使用,要么與try…catch配套使用,要么與throws配套使用。
//使用throws拋出異常 public void f() throws MyException{ try { FileReader reader = new FileReader("G:\\myfile\\struts.txt"); Scanner in = new Scanner(reader); System.out.println(in.next()); } catch (FileNotFoundException e) { throw new MyException("文件沒有找到", e); //throw } }
九、總結
其實對於異常使用的優缺點現在確實存在很多的討論。例如:http://www.cnblogs.com/mailingfeng/archive/2012/11/14/2769974.html。這篇博文對於是否需要使用異常進行了比較深刻的討論。LZ實乃菜鳥一枚,不能理解異常深奧之處。但是有一點LZ可以肯定,那就是異常必定會影響系統的性能。
異常使用指南(摘自:Think in java)
應該在下列情況下使用異常。
1、在恰當的級別處理問題(在知道該如何處理異常的情況下才捕獲異常)。
2、解決問題並且重新調用產生異常的方法。
3、進行少許修補,然后繞過異常發生的地方繼續執行。
4、用別的數據進行計算,以代替方法預計會返回的值。
5、把當前運行環境下能做的事情盡量做完。然后把相同(不同)的異常重新拋到更高層。
6、終止程序。
7、進行簡化。
8、讓類庫和程序更加安全。(這既是在為調試做短期投資,也是在為程序的健壯做長期投資)
更多閱讀:java提高篇-----異常(一)。