Java基础篇:异常机制
目錄:
一、為什么使用異常:
二、異常體系:
三、異常的捕捉--try、catch、finally:
四、關于異常的題:
五、自定義異常:
六、異常鏈:
七、異常的使用誤區:
八:throw、throws:
九:異常使用指南總結:
一、為什么使用異常:
異常的處理機制可以確保我們程序的健壯性,提高系統可用率。在我們的程序設計當中,任何時候任何地方都有可能會出現異常,在沒有異常機制的時候我們是這樣處理的:通過函數的返回值來判斷是否發生了異常(這個返回值通常是已經約定好了的),調用該函數的程序負責檢查并且分析返回值。雖然可以解決異常問題,但是這樣做存在幾個缺陷:
(1) 容易混淆。如果約定返回值為-1時表示出現異常,那么當程序最后的計算結果真的為-1呢?
(2)代碼可讀性差。將異常處理代碼和程序代碼混淆在一起將會降低代碼的可讀性。
(3)由調用函數來分析異常,這要求程序員對庫函數有很深的了解。
在OOP中提供的異常處理機制是提供代碼健壯的強有力的方式。使用異常機制它能夠降低錯誤處理代碼的復雜度,如果不使用異常,那么就必須檢查特定的錯誤,并在程序中的許多地方去處理它,而如果使用異常,那就不必在方法調用處進行檢查,因為異常機制將保證能夠捕獲這個錯誤,并且,只需在一個地方處理錯誤,即所謂的異常處理程序中。這種方式不僅節約代碼,而且把“概述在正常執行過程中做什么事”的代碼和“出了問題怎么辦”的代碼相分離。
二、異常體系:
、
1、Throwable:
從上面這幅圖可以看出,Throwable是java語言中所有錯誤和異常的超類(萬物即可拋)。它有兩個子類:Error、Exception。分別表示錯誤和異常。其中異常Exception分為運行時異常(RuntimeException)和非運行時異常(編譯時異常),也稱之為不檢查異常(Unchecked Exception)和檢查異常(Checked Exception)。
2、Error:
一般是指 Java 虛擬機相關的問題,大多數與代碼編寫與執行操作無關,如系統崩潰、虛擬機出錯誤、動態鏈接失敗、線程死鎖等,這種錯誤無法恢復或不可能捕獲,將導致應用程序中斷,通常應用程序無法處理這些錯誤,因此應用程序不應該捕獲Error對象,也無須在其throws子句中聲明該方法拋出任何Error或其子類。
3、Exception:
Exception是程序可以處理的異常,可以分為運行時異常與非運行時異常:
(1)運行時異常都是RuntimeException類及其子類異常,如NullPointerException、IndexOutOfBoundsException等,這些異常是不檢查異常,程序中可以選擇捕獲處理,也可以不處理。運行時異常表示程序運行過程中可能出現的非正常狀態,這些異常一般是由程序邏輯錯誤引起的,程序應該從邏輯角度盡可能避免這類異常的發生。
出現運行時異常后,如果沒有捕獲處理這個異常(即沒有catch),系統會把異常一直往上層拋,一直到最上層,如果是多線程就由Thread.run()拋出,如果是單線程就被main()拋出。拋出之后,如果是線程,這個線程也就退出了。如果是主程序拋出的異常,那么這整個程序也就退出了。
(2)非運行時異常是RuntimeException以外的異常,類型上都屬于Exception類及其子類。如IOException、SQLException等以及用戶自定義的Exception異常。對于這種異常,JAVA編譯器強制要求我們必需對出現的這些異常進行catch并處理,否則程序就不能編譯通過。所以,面對這種異常不管我們是否愿意,只能自己去寫一大堆catch塊去處理可能的異常。
三、異常的捕捉--try、catch、finally:
對于異常的捕捉,一般使用try、catch、finally 。try快包含著可能出現異常的代碼塊,catch塊捕獲異常后對異常進行處理,finally代碼塊不論程序是否發生異常,總是會執行,所以finally一般用來關閉資源。
接下來看一個很典型的筆試題例子:
public int test2() {int i = 1;try {System.out.println("try語句塊中");return 1;} finally {System.out.println("finally語句塊中");return 2;}} 運行結果是: try語句塊中 finally語句塊中 2從運行結果中可以發現,try中的return語句調用的函數先于finally中調用的函數執行,也就是說return語句先執行,finally語句后執行,所以,返回的結果是2。return并不是讓函數馬上返回,而是return語句執行后,將把返回結果放置進函數棧中,此時函數并不是馬上返回,它要執行finally語句后才真正開始返回。
四、關于異常的題:
1、error和exception有什么區別?運行時異常與編譯時異常的區別?(參考第2題回答)
2、Java中的異常處理機制的簡單原理和應用:
(1)異常是指java程序運行時所發生的非正常情況或錯誤,Java對異常進行了分類,不同類型的異常分別用不同的Java類表示,所有異常的根類為java.lang.Throwable,Throwable下面又派生了兩個子類:Error 和 Exception;
(2)Error 表示應用程序本身無法克服和恢復的一種嚴重問題,一般是指java虛擬機相關的問題,大多數與代碼編寫與執行操作無關,如系統崩潰、內存異常、虛擬機出錯誤、動態鏈接失敗、線程死鎖等,這種錯誤無法恢復或不可能捕獲,通常應用程序無法處理這些錯誤,將導致應用程序中斷。
(3)Exception表示程序還能夠克服和恢復的問題,其中又分為運行時異常和編譯時異常。運行時異常表示程序運行過程中可能出現的非正常狀態,一般是由程序邏輯錯誤引起的,程序應該從邏輯角度盡可能避免這類異常的發生,如數組腳本越界(ArrayIndexOutOfBoundsException),空指針異常(NullPointerException)、類轉換異常(ClassCastException)。非運行時異常是RuntimeException以外的異常,類型上都屬于Exception類及其子類。如IOException、SQLException等以及用戶自定義的Exception異常。
(4)Java為運行時異常和編譯時異常提供了不同的解決方案,編譯器強制編譯時異常必須try..catch處理或用throws聲明繼續拋給上層調用方法處理,否則程序就不能編譯通過,所以普通異常也稱為checked異常,而運行時異常可以處理也可以不處理,所以,編譯器不強制用try..catch處理或用throws聲明,所以系統異常也稱為unchecked異常。
4,請寫出你最常見到的5個runtime exception:
NullPointerException——程序試圖訪問一個空的數組中的元素或訪問空的對象中的 方法或變量時產生異常;
ArithmeticException——由于除數為0引起的異常;
ArrayIdexOutOfBoundsException——訪問數組元素下標越界,引起異常;
IndexOutOfBoundsExcention——由于數組下標越界或字符串訪問越界引起異常;
ClassCastException——當把一個對象歸為某個類,但實際上此對象并不是由這個類 創建的,也不是其子類創建的,則會引起異常;
OutofMemoryException——用new語句創建對象時,如系統無法為其分配內存空 間則產生異常;
ClassNotFoundException——未找到指定名字的類或接口引起異常;
ArrayStoreException——當向數組中存放非數組聲明類型對象時拋出。
NegativeArraySizeException——數組長度是負數,則產生異常;
IllegalMonitorStateException——監控器狀態出錯引起的異常;
SecurityException——由于訪問了不應訪問的指針,使安全性出問題而引起異常;
IOException——由于文件未找到、未打開或者I/O操作不能進行而引起異常;
CloneNotSupportedException——程序中的一個對象引用Object類的clone方法,但此對象并沒有連接Cloneable接口,從而引起異常;
InterruptedException——當一個線程處于等待狀態時,另一個線程中斷此線程,從而引起異常;
NoSuchMethodException——所調用的方法未找到,引起異常;
IllegalAccessExcePtion——試圖訪問一個非public方法;
StringIndexOutOfBoundsException——訪問字符串序號越界,引起異常;
NumberFormatException——字符的UTF代碼數據格式有錯引起異常;
IllegalThreadException——線程調用某個方法而所處狀態不適當,引起異常;
FileNotFoundException——未找到指定文件引起異常;
EOFException——未完成輸入操作即遇文件結束引起異常。
五、自定義異常:
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( 2 / i);}}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拋出交給上級處理,二是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: 文件沒有找到--02at com.test9.Test.g(Test.java:31)at com.test9.Test.main(Test.java:38) Caused by: com.test9.MyException: 文件沒有找到--01at 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: 文件沒有找到--02at com.test9.Test.g(Test.java:31)at com.test9.Test.main(Test.java:38)七、異常的使用誤區:
首先我們先看如下示例:該實例能夠反映java異常的不正確使用:
OutputStreamWriter out = null;java.sql.Connection conn = null;try { // 標注1Statement 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(); //標注2out.close();} catch (Exception ex){ //標注3ex.printStackTrace(); //標注4}1、標注1:
?????? 對于這個try…catch塊,真正目的是捕獲SQL的異常,但是這個try塊是不是包含了太多的信息了。這是我們為了偷懶而養成的代碼壞習慣。有些人喜歡將一大塊的代碼全部包含在一個try塊里面,因為這樣省事,反正有異常它就會拋出,而不愿意花時間來分析這個大代碼塊有那幾塊會產生異常,產生什么類型的異常,反正就是一簍子全部搞定。這就想我們出去旅游將所有的東西全部裝進一個箱子里面,而不是分類來裝,雖不知裝進去容易,找出來難啊!!!所有對于一個異常塊,我們應該仔細分清楚每塊的拋出異常,因為一個大代碼塊有太多的地方會出現異常了。
結論一:盡可能減小try塊!!!
2、標注2:
?????? 在這里你發現了什么?異常改變了運行流程!!不錯就是異常改變了程序運行流程。如果該程序發生了異常那么conn.close(); out.close();是不可能執行得到的,這樣勢必會導致資源不能釋放掉。所以如果程序用到了文件、Socket、JDBC連接之類的資源,即使遇到了異常,我們也要確保能夠正確釋放占用的資源。這里finally就有用武之地了:不管是否出現了異常,finally總是有機會運行的,所以finally用于釋放資源是再適合不過了。
結論二:保證所有資源都被正確釋放,充分運用finally關鍵詞!!!
3、標注3:
?????? 對于這個代碼我想大部分人都是這樣處理的。使用這樣代碼的人都有這樣一個心理,一個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)封裝異常。這是我認為最好的處理方法,對異常信息進行分類,然后進行封裝處理。
(4)不要捕獲異常。
4.2、異常信息不明確。我想對于這樣的:java.io.FileNotFoundException: ………信息除了我們IT人沒有幾個人看得懂和想看吧!所以在出現異常后,我們最好能夠提供一些文字信息,例如當前正在執行的類、方法和其他狀態信息,包括以一種更適合閱讀的方式整理和組織printStackTrace提供的信息。起碼我公司是需要將異常信息所在的類、方法、何種異常都需要記錄在日志文件中的。
結論四:既然捕獲了異常,就要對它進行適當的處理,不要捕獲異常之后又把它丟棄,不予理睬!!!
結論五:在異常處理模塊中提供適量的錯誤原因信息,組織錯誤信息使其易于理解和閱讀!!!
?????? 對于異常還有以下幾個注意地方:
結論六:不要在finally塊中處理返回值!!!
結論七:不要在構造函數中拋出異常!!!
八: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} }九:異常使用指南總結:
應該在下列情況下使用異常(摘自:Think in java):
?????? 1、在恰當的級別處理問題(在知道該如何處理異常的情況下才捕獲異常)。
?????? 2、解決問題并且重新調用產生異常的方法。
?????? 3、進行少許修補,然后繞過異常發生的地方繼續執行。
?????? 4、用別的數據進行計算,以代替方法預計會返回的值。
?????? 5、把當前運行環境下能做的事情盡量做完。然后把相同(不同)的異常重新拋到更高層。
?????? 6、終止程序。
?????? 7、進行簡化。
?????? 8、讓類庫和程序更加安全。(這既是在為調試做短期投資,也是在為程序的健壯做長期投資)
參考博客以及轉載博客地址:
https://blog.csdn.net/chenssy/article/details/17651909
https://blog.csdn.net/qq_22860341/article/details/73610537
https://blog.csdn.net/huhui_cs/article/details/38817791
https://blog.csdn.net/chenssy/article/details/17651971
總結
以上是生活随笔為你收集整理的Java基础篇:异常机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java基础篇:final关键字
- 下一篇: Java基础篇:数组