Java探索之旅(16)——异常处理
1.異常與異常處理
? ? 在《java編程思想》中這樣定義?異常:阻止當前方法或作用域繼續執行的問題。雖然java中有異常處理機制,但是要明確一點,決不應該用"正常"的態度來看待異常。絕對一點說異常就是某種意義上的錯誤,就是問題,它可能會導致程序失敗。之所以java要提出異常處理機制,就是要告訴開發人員,你的程序出現了不正常的情況,請注意。
? ??異常就是一個表示組織執行正常進行的錯誤(情況)。異常沒有處理,程序將非正常終止。這是Java魯棒性的一個體現。異常處理最根本的優勢或者目的:即將檢測錯誤和處理錯誤分離,被調用的方法檢測錯誤而調用此方法的程序(方法)處理錯誤。
? ? 常見的異常有2種構造方法,默認或者字符串參數。
? ?如下:兩個數除法的運算,實現了被除數為0的異常處理
import java.util.*; public class Exception {public static void main(String args[]){Scanner input=new Scanner(System.in);int a,b;boolean flag=true;do{try{System.out.println("input a,b");a=input.nextInt();b=input.nextInt();int result=division(a,b);System.out.println("a/b = "+result);flag=false;}catch(ArithmeticException ex){//處理錯誤System.out.println(ex);input.nextLine();//清行,等待輸入}}while(flag);input.close();}public static int division(int a,int b)//檢錯錯誤{if(b!=0)return a/b;else throw new ArithmeticException(" b cannot be zero ,input again");}}2.異常類型與體系
?Throwable是所有異常的根。所有的異常類直接或者間接繼承于它,體系如下。
異常類一般分為如下3類
? ? ?Error?系 統錯誤(system error)類:JVM生成和拋出。如LinkageError,編譯產生的不兼容問題,內存泄露,死循環等。此種情況程序本身無法處理,只能由外來程序干預。 ? ? ?Exception?異常類:描敘程序和外部環境引起錯誤,這些錯誤能夠被捕獲。 ? ???Runtime Excepiton?運行時異常類:為免檢異常。描敘程序編寫錯誤,如錯誤類型、越界、空指針等。一般由JVM拋出。包括IllegalArgumentException、IllegalStateException、NullPointerException、IndexOutOfBoundsException等等也可以分為2類:運行時異?;蛘呙鈾z查異常,非運行時異?;蛘弑貦z異常?? ? ? ?免檢異常Unchecked Exception:僅僅包括Runtime Excepiton、Error及其子類。一般為程序設計上存在不可恢復的邏輯錯誤。對免檢異??梢圆蛔鎏幚?#xff0c;JVM會處理。 ? ? ?必檢異常Checked Exception:其他所有的異常,即狹義的異常,必須被被程序捕獲和處理。如IOException,ClassNotFoundException及其子類。為try...catch...所顯示的捕獲,編譯器強制程序員檢查并處理即必檢之含義(此就是免檢或非免檢的差別)。
3.異常的捕獲和處理?常捕yi獲和處理框架:
? 3.1處理和捕獲框架? ?
? ? ?try語句塊:嘗試運行代碼,try語句塊中代碼受異常監控,其中代碼(或者調用的方法)發生異常時,會拋出異常對象。?
? ? ?catch語句塊:會捕獲try代碼塊中發生的異常并在其代碼塊中做異常處理,catch語句帶一個Throwable類型的參數表示可捕獲異常類型。當try中出現異常時,catch會捕獲到發生的異常并匹配。catch語句可以有多個,用來匹配多個中的一個異常。一旦匹配上后,就不再嘗試匹配別的catch塊了。由于catch可以捕獲某個父類異常及其所有派生子類。因此應該保證子類catch塊在父類的catch塊之前,否則產生編譯錯誤。
? ? ?finally語句塊:這一語句塊可以省略。?是緊跟catch語句后的語句塊,這個語句塊總是會在方法返回前執行,?而不管是否try語句塊是否發生異常。并且這個語句塊總是在方法返回前執行。?目的是給程序一個補救的機會。這樣做也體現了Java語言的健壯性,具體見4.3節
try{//(嘗試運行的)程序代碼}catch(異常類型 異常的變量名){//異常處理代碼}finally{//異常發生,方法返回之前,總是要執行的代碼} ? ? ? ?三個語句塊均不能單獨使用,可以組成形如: ? ? ? ? try...catch...finally ? ? ? ? try...catch ? ? ? ? try...finally ? ? 3種結構,catch語句可以有一個或多個,finally語句最多一個。局部變量獨立而不能相互訪問。多個catch塊時候,由上到下匹配,一旦成功某個異常類即執行catch塊代碼,且不再執行其他catch塊。?3.2throw和throws關鍵字?
? ??throw關鍵字:用于方法體內部,用來拋出一個Throwable類型的異常。如果拋出了某種檢查異常,?則還在方法頭部用throws對應聲明。該方法的調用者必須檢查處理拋出的異常。如果所有方法都層層上拋獲取的異常,最終JVM會進行處理,處理也很簡單,就是打印異常消息和堆棧信息。如果拋出免檢異常(Error或RuntimeException),則該方法的調用者可選擇是否處理該異常。? ? ? ?如果異常處理器沒有處理某異常,Java容許異常處理器重新拋出該異常。? ??throws關鍵字:用于方法體外部的方法聲明部分,用來聲明方法可能會拋出某些異常。僅當拋出了檢查異常,該方法的調用者才必須處理或者重新拋出該異常。當方法的調用者沒有處理該異常的時候,應該繼續拋出。注意,如果方法沒有在父類中聲明異常,那么不能在子類中對其進行覆蓋來聲明異常。
public static void test3() throws Exception{ //拋出一個檢查異常 throw new Exception("方法test3中的Exception"); }
?3.3 異常說明?
? 常見異常類的成員函數(繼承自Throwable)如下: ?? ? ? ?String getCause():返回引起異常的原因(如引起該異常的異常)。如果cause不存在或未知則返回 null。? ? ???String getMessage():返回異常的消息信息,即新建異常時候的String參數。? ? ???String toString(); ?返回“異常類全名:getMessage()方法返回字符串” ? ???void ?printStackTrace():對象的堆棧跟蹤輸出至錯誤輸出流 ? ? ?StackTraceElement[] ?getStackTrace():返回棧跟蹤元素數組來表示可拋出的棧跟蹤信息??梢哉{用子函數確定異常發生的類(getClassName()),發生異常的方法(getMeholdName()),發生的行號(getLineNumber())等信息。對于嵌套調用,元素0為棧頂元素,表示調用序列的最后一個調用(異常被拋出的地方),最后一個棧元素為調用序列的最后一個調用。如下面的非嵌套調用,則倒序輸出所有的異常。 public class NeverCaught {public static void main(String[] args) throws Exception {try { function1();} catch (Exception ex) {System.out.println("getCause()----"+ex.getCause());//此處異常是由“exception from function2”引起System.out.println("getMessage()----"+ex.getMessage());System.out.println("toString()----"+ex.toString()+"\n"); ex.printStackTrace();//打印調用棧的跟蹤信息StackTraceElement[] ste=ex.getStackTrace();for(int i=0;i<ste.length;i++){System.out.print("\n"+ste[i].getMethodName()+":");//出現異常的方法名System.out.println(ste[i].getLineNumber());//出現異常的所屬類名System.out.println(ste[i].getClassName());}//出現異常的行號} } static void function1() throws Exception {try {function2();} catch (Exception ex) {Exception C= new Exception("exception from function1",ex);throw C;} }static void function2() throws Exception{throw new Exception("exception from function2");? ?輸出:
4.常見的異常處理機制
?4.1異常處理器及執行的語句
? ?如果try中語句拋出異常,而本函數沒有catch處理,則將傳遞異常給調用此函數的方法,且本函數的其他代碼將不會執行。如果上一級函數仍舊沒有catch處理,則這一級函數的其他代碼不會執行的同時,傳遞該異常給再上一級調用函數。即所謂的反向傳播檢查。如果至main函數仍舊未有處理異常,JVM會進行處理。
? ?一旦某一級處理器捕獲到這個異常,則僅僅執行該函數catch的代碼和try-catch結構之外的代碼。調用該方法的上一級函數的try和try-catch之外的代碼將被執行。
? 例如:
? ? ? ①function3拋出3中異常Exception3,Exception2,Exception1之一
? ? ? ②調用關系main<--function1<---function2<-----function3
? ? ? ③main,function1(),function2()分別處理異常Exception3,Exception2,Exception1
? 4.2 異常鏈的使用及異常丟失
? ? 參考2中提到了異常處理的丟失情況。假設這樣的場景:倘若某個異常B被捕捉到,且繼而拋出其他異常C,則異常B的信息將不會出現在C的跟蹤棧StackTrace上,不利于檢查。因此使用從Throwable繼承的initCause()方法,即異常鏈特性。
? ? ?先定義異常類ExceptionB,ExceptionC
public class ExceptionB extends Exception {public ExceptionB(String str) {super(str);} }public class ExceptionC extends Exception<strong>B</strong> {public ExceptionC(String str) {super(str);} }? ? ? ? ? ? ??使用異常鏈 public class NeverCaught {public static void main(String[] args) {//處理異常Ctry { function1();} catch (ExceptionC ex) {ex.printStackTrace();} } static void function1() throws ExceptionC {//處理異常ExceptionB,拋出異常ExceptionCtry {function2();} catch (ExceptionB ex) {ExceptionC C= new ExceptionC("exception C");//異常鏈C.initCause(ex);//初始化引起C異常的異常throw C;} }? ? ? 倘若沒有使用異常鏈C.initCause(ex);則main()中的ex.printStackTrace()僅僅顯示出現異常ExceptionC,而添加之后,ExceptionC,ExceptionB二者先后顯示。
? ??異常鏈同原始異常一起拋出新的異常,也稱為鏈式異常。還有一種利用構造法,如:
public class NeverCaught {public static void main(String[] args) throws Exception {try { function1();} catch (Exception ex) {ex.printStackTrace();} } static void function1() throws Exception {try {function2();} catch (Exception ex) {Exception C= new Exception("exception from function1",ex);//包裝成新的異常throw C;} }static void function2() throws Exception{throw new Exception("exception from function2");} }? ? 第11行代碼利用構造法包裝新的異常。控制臺中先顯示"exception from function1"后顯示原始異常"exception from function2",2次異常都被檢測到。
?4.3異常轉譯
? ? 所謂的異常轉譯就是將一種異常轉換另一種新的異常,也許這種新的異常更能準確表達程序發生異常。在Java中有個概念就是異常原因,異常原因導致當前拋出異常的那個異常對象,幾乎所有帶異常原因的異常構造方法都使用Throwable類型做參數,這也就為異常的轉譯提供了直接的支持,因為任何形式的異常和錯誤都是Throwable的子類。
? ? 比如將SQLException轉換為另外一個新的異常DAOException,先自定義異常DAOException。
DAOException daoEx = new DAOException ( "SQL異常", sqle);
? ? ? ? ?異常轉譯是針對所有繼承Throwable超類的類而言的,從編程的語法角度講,其子類之間都可以相互轉換。但是,從合理性和系統設計角度考慮,可將異常分為三類:Error、Exception、RuntimeException。參考1認為,合理的轉譯關系圖應該如圖:
? ? 參考1的作者認為:對于一個應用系統來說,?系統所發生的任何異常或者錯誤對操作用戶來說都是系統"運行時"異常,都是這個應用系統內部的異常。這也是異常轉譯和應用系統異??蚣茉O計的指導原則。在系統中大量處理非檢查異常的負面影響很多,?最重要的一個方面就是代碼可讀性降低,程序編寫復雜,異常處理的代碼也很蒼白無力。?因此,很有必要將這些檢查異常Exception和錯誤Error轉換為RuntimeException異常讓程序員根據情況來決定是否捕獲和處理所發生的異常。?
??? 圖中的三條線標識轉換的方向,分三種情況:? ? ? ? ? ? ? ? ①:Error到Exception:將錯誤轉換為異常,并繼續拋出。例如Spring WEB框架中將org.springframework.web.servlet.DispatcherServlet的doDispatch()方法中,?將捕獲的錯誤轉譯為一個NestedServletException異常。這樣做的目的是為了最大限度挽回因錯誤發生帶來的負面影響。?因為一個Error常常是很嚴重的錯誤,可能會引起系統掛起。? ? ? ? ? ? ? ? ②:Exception到RuntimeException:將必檢異常轉換為RuntimeException可以讓程序代碼變得更優雅,?讓開發人員集中經理設計更合理的程序代碼,反過來也增加了系統發生異常的可能性。? ? ? ? ? ? ? ? ③:Error到RuntimeException:目的還是一樣的。把所有的異常和錯誤轉譯為免檢異常,?這樣可以讓代碼更為簡潔,還有利于對錯誤和異常信息的統一處理。??4.4?finally子句和文件讀寫清理
? ? 不論異常是否出現或者捕獲與否,finally塊子句都會被執行。甚至在finally之前出現return語句,它也會被執行。當有除內存之外的資源回復初始狀態時使用該子句。如已經打開的文件等。例如修改4.1中的代碼如下:
public class ExceptionList {public static void main(String[] args) throws Exception3, Exception2 {//處理異常 Exception1.............finally {System.out.println("執行 statement2 code block");}}public static void function1(int a) throws Exception3,Exception2,Exception1//處理異常 Exception2{............/*statement4 code block*/finally {System.out.println("執行 statement4 code block");}}public static void function2(int a) throws Exception3,Exception2,Exception1//處理異常 Exception3{............/*statement6 code block*/finally {System.out.println("執行 statement6 code block");}}public static void function3(int a) throws Exception3,Exception2,Exception1//拋出異常{...........} } ? 倘若function3()拋出Exception1。輸出: ? ? ?執行 statement6 code block ? ? ?執行 statement4 code block ? ? ?檢測到 Exception1 ? ? ?執行 statement2 code block? ? ? ?finally子句常見于I/O程序設計。為了確保文件在任何情況下得到關閉,建議在其中放入文件關閉語句。參考2中提到:Try...finally結構是保證資源正確關閉的一個手段。如果你不清楚代碼執行過程中會發生什么異常情況會導致資源不能得到清理,那么你就用try對這段"可疑"代碼進行包裝,然后在finally中進行資源的清理。?
? ??例如:原始代碼如下
public void readFile() {BufferedReader reader = null;try {reader = new BufferedReader(new InputStreamReader(new FileInputStream("file")));// do some other work//close readerreader.close();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} }? ? ? ??因為程序會在異常出現的地方跳出,故而后面的代碼將不能執行。所以以上雖然及早的關閉reader,但是reader.close()以前異常隨時可能發生,因此不能預防任何異常的出現。這時我們就可以用try...finally來改造成為: public void readFile() {BufferedReader reader = null;try {try {reader = new BufferedReader(new InputStreamReader(new FileInputStream("file")));// do some other work// close reader} finally {reader.close();}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}? ? ? 異常缺失:finally子句會導致異常丟失。例如,在try.....finally模型中:finally中也拋出的新異常會是try中拋出的異常被取代。又例如,finally中出現retrun語句會導致導致try中拋出的異常被屏蔽
?4.5多個catch塊和catch排列規則
? ? 單個方法的catch檢查中,從上到下檢查各個catch塊。若某個catch塊父類異常的,則也能catch子類異常。因此應該保證子類catch塊出現在父類catch塊之前。否則出現編譯錯誤。
public class Sequence {public static void main(String[] args){try{method(2);}catch(ExceptionB b){System.out.println("getMessage()----"+b.getMessage());}catch(ExceptionA a){System.out.println("getMessage()----"+a.getMessage());}} public static void method(int a) throws ExceptionA//即拋出父類異常又拋出子類異常,聲明拋出父類異常即可{if(a==1)throw new ExceptionA("ExceptionA appears");elsethrow new ExceptionB("ExceptionB appears");} } class ExceptionA extends Exception {public ExceptionA(String str) {super(str);} }class ExceptionB extends ExceptionA {public ExceptionB(String str) {super(str);} }? ? ? ? ?如上代碼。子類catch塊在前,父類catch塊在后。父類catch可以捕獲子類異常處理。同樣,倘若某個函數即拋出父類異常又拋出子類異常,聲明拋出父類異常即可。
? ?4.6異常的限制
? ??對于某一類。不同參數的構造函數可以拋出不同的異常。但是必須聲明拋出基類異常。
? ??基類函數未聲明拋出異常,則子類重載函數不能聲明拋出。
? ??單若基類拋出異常,則可以重載不拋出異?;蛘?span style="font-family:'KaiTi_GB2312';font-size:18.3999996185303px;">拋出繼承于基類異常之異常的函數。
? ??接口同名函數不能改變基類已經存在的函數的異常類型。
? ?簡言之:在繼承和覆蓋中,“異常說明”的接口不是變大而是變小了。例如出現在基類的異??梢圆灰欢ǔ霈F在子類。這樣從側面說明子類的功能更加細化。
?4.7構造器與異常
? ?使用構造器時,如打開文件。應使用規則:在最外層,使用try...catch塊。try中創建成功后,立即進入再一級try..finally(或try...catch--finally)塊,在finally語句中再使用釋放功能。創建失敗,由外層catch塊處理。
5.總結
?5.1何時使用異常
? ??try包含正常情況執行的代碼,catch包含異常情況下執行的代碼。異常處理將錯誤處理代碼從正常的程序設計分離出來,因此更易讀和修改。但是由于異常處理需要初始化異常對象,需要從調用棧返回,而且還要沿方法調用鏈傳播異常以便找到其他異常處理器,因此需要很多醫院和時間。
? ??盡量在發生異常的地方處理異常。共同的異常應當考慮將其包裝成同一異常類。
? ??異常是程序設計的一部分,不要為了使用異常而使用異常。特別是不要將異常處理當簡單的邏輯測試使用,例如NullPointException某種情況下可以直接使用if-else判斷。
?5.2異常處理的原則
? ??能處理就早處理,拋出不去還不能處理的就想法消化掉或者轉換為RuntimeException處理。?因為對于一個應用系統來說,拋出大量異常是有問題的,應該從程序開發角度盡可能的控制異常發生的可能。
? ??對于檢查異常,如果不能行之有效的處理,還不如轉換為RuntimeException拋出。這樣也讓上層的代碼有選擇的余地――可處理也可不處理。??異??梢詡鞑?#xff0c;也可以相互轉譯,但應該根據需要選擇合理的異常轉譯的方向
? ??對于一個應用系統來說,應該有自己的一套異常處理框架,這樣當異常發生時,也能得到統一的處理風格,將優雅的異常信息反饋給用戶。
?5.3異常的誤用
? ?參考2中提到:
? ??避免用一個Exception(Throwable)來捕捉所有的異常,
? ??異常是程序處理意外情況的機制,當程序發生意外時,我們需要盡可能多的得到意外的信息,包括發生的位置,描述,原因等等。這些都是我們解決問題的線索。但是上面的例子都只是簡單的printStackTrace()。如果我們自己寫代碼,就要盡可能多的對這個異常進行描述。比如說為什么會出現這個異常,什么情況下會發生這個異常。如果傳入方法的參數不正確,告知什么樣的參數是合法的參數,或者給出一個sample。
? ??將try?block寫的簡短,不要所有的東西都扔在這里,我們盡可能的分析出到底哪幾行程序可能出現異常,只是對可能出現異常的代碼進行try。盡量為每一個不同的異常寫一個try...catch,避免異常丟失。在IO操作中,一個IOException也具有"一夫當關萬夫莫開"的氣魄。
? 5.4 其它
? ? ? ??在捕獲某種異常后,直接拋出另外一類異常。則垃圾回收機制會將棧中的異常對象清理干凈,printStackTrace()顯示從異常由此拋出。見《Think in Java》p260?
? ? ? ??相較于C和C++,異常能夠容許程序在發生錯誤后強制停止并且換一條路徑走,并告訴我們出了什么問題增加程序的穩健性,C則不行。
? ? ? ??new在堆上創建異常對象,并返回該對象的引用。當前路徑終止并在開始查找異常處理程序。
? ? ? ??可以聲明某個方法拋出異常,但實際不拋出。定義抽象基類和接口時往往需要實現拋出這些預聲明的異常。
? ? ? ??C++無finally子句,依賴析構函數達到同樣的目的。
? ? ? ??可以直接在main函數中聲明拋出異常,但不做處理,異常信息自動傳遞給控制臺。
? ? ? ??可以使用RuntimeException(e)的形式包裝必檢異常,然后重新拋出。再使用.getCause()的方式取出。具體見《Thinking in Java》P280
? ? ? ?
本文參考:
? 1.Java異常體系結構
? 2.淺談java異常[Exception]
轉載于:https://www.cnblogs.com/engineerLF/p/5393078.html
總結
以上是生活随笔為你收集整理的Java探索之旅(16)——异常处理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Data Member 的绑定
- 下一篇: activity简要笔记