摘要:一拋出異常發(fā)現(xiàn)錯誤異常也是對象使用使用異常機制來報告錯誤。異常也是普通的類類型。異常聲明中的語句執(zhí)行完成后會繼續(xù)執(zhí)行后的其他語句。非檢查異常拋出到上一級時可以不用進行聲明合理的使用非檢查異常可以簡化代碼。
為什么需要異常機制:
Java的基本理念是“結(jié)構(gòu)不佳的代碼不能運行” --- Java編程思想
最理想的是在編譯時期就發(fā)現(xiàn)錯誤,但一些錯誤要在運行時才會暴露出來。對于這些錯誤我們當(dāng)然不能置之不理。對于錯誤而言的兩個關(guān)鍵是發(fā)現(xiàn)和處理錯誤。Java提供了統(tǒng)一的異常機制來發(fā)現(xiàn)和處理錯誤。
不考慮異常存在來看一下這個場景:
public void showObject(Object obj) { if (obj == null) { System.out.println("error obj is null"); } else { System.out.println(obj.toString()); } }
對于showObject來說obj為null是一個錯誤,要在輸出之前做錯誤判斷,發(fā)生錯誤的話把錯誤打印出來作為錯誤報告和處理。這里把錯誤的發(fā)現(xiàn)、報告處理和正常業(yè)務(wù)邏輯放在了一起。但一些錯誤往往比這復(fù)雜且不只一個,如果我們?yōu)槊恳粋€錯誤都去定義一個獨特錯誤報告的形式且都將錯誤處理代碼和正常業(yè)務(wù)代碼緊緊的耦合在一起那我們代碼會變得難以維護。
合理的使用異常不僅能使我們的代碼更加健壯,還能簡化開發(fā)提升開發(fā)效率。
一、拋出異常(發(fā)現(xiàn)錯誤):java使用使用異常機制來報告錯誤。異常也是普通的類類型。Java自身已經(jīng)定義好了使用java時可能會產(chǎn)生的異常,使用java時java會自動去檢查異常的發(fā)生,當(dāng)異常發(fā)生時,java會自動創(chuàng)建異常對象的實例并將其拋出。我們經(jīng)常看到的NullPointerException便是java已經(jīng)定義好的異常。
除了java自身定義的異常外我們可以自定義異常,但自定義的異常需要我們自己去檢查異常情形的發(fā)生,并自己創(chuàng)建異常對象和拋出。當(dāng)然也可以創(chuàng)建java自定義的異常并拋出,拋出異常使用throw關(guān)鍵字:
throw new Exception();
我們使用的第三方庫大多封裝了自己的異常,并在異常情形發(fā)生時將自定義異常通過throw拋出。所有的異常類型都繼承自Throwable類,所有Throwable類型的對象都可被拋出。
異常發(fā)生,系統(tǒng)自動創(chuàng)建異常實例并拋出,或我們自己創(chuàng)建異常實例拋出異常時,代碼的正常執(zhí)行流程將會被終止,轉(zhuǎn)而去執(zhí)行異常處理代碼。
二、異常捕獲(處理錯誤):當(dāng)異常拋出時,自然拋出的異常應(yīng)該得到處理,這就需要將拋出的捕獲異常。但一個異常類型可能在很多地方被拋出,那么怎么去對特定的地方編寫特定的異常處理程序那?java采用一個最方便和合理的方式,即對可能產(chǎn)生異常的代碼區(qū)域進行監(jiān)控,并在該區(qū)域后添加處理程序。
監(jiān)控的代碼區(qū)域放在try{}中,而異常處理代碼緊跟在try后的catch中:
try { /***/ } catch (ExceptionType e) { /* *** */ }
catch類似方法的申明括號中為異常的類型和該類類型的實例。表明當(dāng)前catch塊處理的是什么類型的異常,而e便是該異常拋出的實例對象。
當(dāng)try內(nèi)的代碼拋出異常時,就會停止當(dāng)前流程,去匹配第一個catch中申明的異常類型與拋出類型相同的catch,如果匹配到則執(zhí)行其內(nèi)代碼。一個try中可能會拋出多種類型的異常,可以用多個catch去匹配。
注意catch中聲明的異常如果為當(dāng)前拋出異常的父類型也可以匹配。所以一般將基類的異常類型放在后面。
因為所有可以進行捕獲的異常都繼承自Exception,所有可以catch中申明Exception類型的異常來捕獲所有異常,但最后將其放在最后防止將其他異常攔截了。
三、重新拋出異常先看一下異常的類層次結(jié)構(gòu)圖:
我們可以將異常分為檢查和非檢查兩種類型:
檢查異常:該類異常包含Exception及其子類異常,這些類型的異常的拋出必須有相應(yīng)的catch進行捕獲,否則無法通過編譯。
非檢查異常:該類型的異常包含RuntimeException、Error和兩者的子類型,這類異常可以沒有對應(yīng)的try-catch進行捕獲也可通過編譯。當(dāng)異常發(fā)生時沒有相應(yīng)的捕獲則異常會自動向上一級拋出,如此如果一直到main方法中還未被捕獲則會調(diào)用該異常的printStacjTrace方法輸出異常信息,并終止main的運行。其中Error為Java自身錯誤,這類錯誤發(fā)生時我們并不能在業(yè)務(wù)代碼中去解決,如內(nèi)存不足,所以這類異常不需要去捕獲。
catch中的語句執(zhí)行完成后會繼續(xù)執(zhí)行try-catch后的其他語句。所以當(dāng)try-catch后還有語句時,一定要保證但異常發(fā)生時在catch中已經(jīng)對異常進行了正確處理,后面的代碼可以得到正常的運行,如果不能保證則應(yīng)該終止代碼向后的執(zhí)行或再次拋出異常。
一些異常在當(dāng)前的方法中不需要或無法進行處理時,可以將其拋出到上一層。要在方法中將異常拋出需要在方法中對要拋出的異常進行聲明,這樣方法的調(diào)用者才能知道哪些異常可能會拋出,從而在調(diào)用方法時添加異常處理代碼。
非檢查異常拋出到上一級時可以不用進行聲明,合理的使用非檢查異常可以簡化代碼。
在方法聲明的參數(shù)列表之后使用throws進行異常聲明,多個異常類型使用逗號隔開:
void t () thrwos ExcptionTyep1, ExceptionType2 { }
在方法中聲明了的異常在方法中可以不進行捕獲,直接被拋出到上一級。異常聲明父類異常類型可以匹配子類異常類型,這樣當(dāng)有多個子類異常拋出時,只用聲明一個父類異常即可,子類異常將被自動轉(zhuǎn)換為父類型。
四、創(chuàng)建自定義異常:要創(chuàng)建自己的異常必須得繼承自其它的異常,一般繼承Exception創(chuàng)建檢測異常,繼承RumtimeException創(chuàng)建非檢查異常。
一般情況下異常提供了默認(rèn)構(gòu)造器和一個接受String參數(shù)的構(gòu)造器。對于一般自定義的異常來說,只需要實現(xiàn)這兩個構(gòu)造方法就足夠了,因為定義異常來說最有意義的是異常的類型,即異常類的名字,但當(dāng)異常發(fā)生時只需看到這個異常的類型就知道發(fā)生了什么,而其他一些操作在Throwable中已經(jīng)有定義。所以除非有一些特殊操作,不然在自定義異常時只需只需簡單的實現(xiàn)構(gòu)造方法即可。
五、異常信息:所以異常的根類Throwable定義了我們需要的大多數(shù)方法:
// 獲取創(chuàng)建異常時傳入的字符串 String getMessage() // 使用System.err輸出異常發(fā)生的調(diào)用棧軌跡 void printStackTrace() // 使用傳入的PrintStream打印異常調(diào)用棧 void printStackTrace(PrintStream s) // 使用PrintStreamOrWriter打印異常調(diào)用棧 void printStackTrace(PrintStreamOrWriter s)
獲取調(diào)用棧實例
StackTraceElement[] getStackTrace()
該方法放回StackTraceElement數(shù)組,StackTraceElement為調(diào)用方法棧的實例,改類型有以下常用方法:
// 返回棧代碼所在的文件名 String getFileName() // 返回異常拋出地的行號 int getLineNumber() // 返回棧的類名 String getClassName() // 放回棧的方法名 String getMethodName()六、異常鏈:
當(dāng)我們捕獲到一個異常時可能想將他在次拋出,但這樣直接拋出的話異常的棧信息是該異常原來的棧信息,不會是最新的再次拋出的異常的棧信息。如下:
class SimpleException extends Exception { public SimpleException() { } public SimpleException(String msg) { super(msg); } } public class Test { public void s() throws SimpleException { throw new SimpleException(); } public void s2() throws SimpleException { try { s(); } catch(SimpleException e) { throw e; } } public static void main(String[] args) { Test t = new Test(); try { t.s2(); } catch (SimpleException e) { e.printStackTrace(); } } }
上面代碼輸出為:
com.ly.test.javatest.exceptiontest.SimpleException at com.ly.test.javatest.exceptiontest.Test.s(Test.java:19) at com.ly.test.javatest.exceptiontest.Test.s2(Test.java:24) at com.ly.test.javatest.exceptiontest.Test.main(Test.java:33)
可以看到異常拋出最終地為 com.ly.test.javatest.exceptiontest.Test.s(Test.java:19),但如果我們想讓異常拋出地變?yōu)閟2那?畢竟我們在這里自己拋出了異常。
Thrwoable類的fillInStackTrac創(chuàng)建一個新的Throwable對象,并將當(dāng)前棧信息做新創(chuàng)建的Throwable異常的異常棧信息,然后返回。
上面的做法又有另外一個問題,如果我們使用fillInStackTrace獲得新的異常,那原來的異常信息也就丟失了,如果我們想拋出新的異常當(dāng)又得包含原來的異常那?
Error、Exception和RuntimeException都含有一個接受Throwable對象的構(gòu)造方法,在創(chuàng)建新的異常時時傳入原來異常,即可保存原來異常。需要時使用getCause來獲取到。除了使用構(gòu)造方法傳入異常,還可使用initCase方法傳入異常。這其中的潛臺詞是“改異常是由什么異常造成的”。如下:
public class Test { public void s() throws Exception { throw new Exception(); } public void s2() throws Exception { try { s(); } catch(Exception e) { Exception ne = (Exception)e.fillInStackTrace(); ne.initCause(e); throw ne; } } public static void main(String[] args) { Test t = new Test(); try { t.s2(); } catch (Exception e) { e.printStackTrace(); } } }六、總是執(zhí)行的finally
看一下面的代碼:
public class Test { public static void s() throws IOException { throw new IOException(); } public static void main(String[] args) { String fileName = "C: emp est.txt"; File file = new File(fileName); InputStream in = null; try { in = new FileInputStream(file); s(); int tempbyte = in.read(); in.close(); } catch (IOException e) { if (in != null) { System.out.println("in"); } e.printStackTrace(); } } }
可以看到要對in進行close但正常的流程中發(fā)生了異常,導(dǎo)致正常流程中的in.close無法執(zhí)行,便跳到cattch中去執(zhí)行,上面于是又在catch中寫了一個關(guān)閉。著只是一個簡單清理操作,但如果需要執(zhí)行的清理操作不止一行而是非常多那?也是在正常流程和catch中寫兩遍嗎,這樣是非常不友好的,所以java提供了finally,如下
public class Test { public static void s() throws IOException { throw new IOException(); } public static void main(String[] args) { String fileName = "C: emp est.txt"; File file = new File(fileName); InputStream in = null; try { in = new FileInputStream(file); s(); int tempbyte = in.read(); } catch (IOException e) { e.printStackTrace(); } finally { if (in != null) { System.out.println("in"); } } } }
finally中的代碼無論異常是否發(fā)生都會被執(zhí)行,即使try中包含return語句,也會在放回之前執(zhí)行finally語句。
對于清理操作和一些異常發(fā)生也必得到執(zhí)行的代碼都應(yīng)該放到finally中。
上面介紹使用finally來釋放資源,但看下面這個情形:
public void test() { try { in = new BufferedReader(new FileReader()); String s = in.readLine(); } catch (FileNotFoundException e) { } catch (Exception e) { try { in.close(); } catch (IOException e2) { System.out.println("in class false"); } } finally { //in.close(); } }
這個例子可以看到如果new FileReader拋出了FileNotFoundException,那么in是不會被創(chuàng)建的,如果此時還在finally中執(zhí)行in.close()那么自然是行不同的。但如果拋出了IOExceptin異常,那么說明in成功創(chuàng)建但在readLine時發(fā)生錯,所以在catch中進行close時in肯定已經(jīng)被創(chuàng)建。這種情形資源的釋放應(yīng)該放到catch中。
public class Test { public static void main(String[] args) { try { int i = throwException(); System.out.println(i); } catch (Exception e) { e.printStackTrace(); } } public static int throwException () throws Exception { try { throw new Exception(); } catch (Exception e) { throw e; } finally { return 1; } } }
上面代碼輸出:1
public class Test { public static void main(String[] args) { try { int i = throwException(); System.out.println(i); } catch (Exception e) { e.printStackTrace(); } } public static int throwException () throws Exception { try { throw new Exception(); } catch (Exception e) { throw e; } finally { throw new NullPointerException(); } } }
上面代碼輸出為:
java.lang.NullPointerException at com.ly.test.javatest.Test.throwException(Test.java:20) at com.ly.test.javatest.Test.main(Test.java:7)
可以看到main中捕獲到的是NullPointerException,首先拋出的Exception異常丟失了。
在開發(fā)中非特殊情形應(yīng)避免以上兩種情況的出現(xiàn)。
八、異常限制:父類構(gòu)造器中聲明的異常在基類的構(gòu)造器中必須也聲明,因為父類的構(gòu)造器總是會顯示會隱式(默認(rèn)構(gòu)造器)的被調(diào)用,而在子類構(gòu)造器中是無法捕獲父類異常的。但子類可以添加父類中沒有聲明的異常。
重載方法時子類只可拋出父類中聲明的異常,因為我們會將子類對象去替換基類,這時如果重載的方法添加類新的異常聲明,那么原來的異常處理代碼將無法再正常工作。但子類方法可以減少或不拋出父類方法聲明的異常。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/68658.html
摘要:沒有操作符重載。最終類型在所有情況下應(yīng)該是默認(rèn)的,并用作為修飾符。這樣就會減少現(xiàn)在你會在和一些第三方的中見到的那些令人困惑的歷史遺留方法。在用過或是最新的之后你會覺得非常的繁瑣。這是最常見的關(guān)于的吐槽,但它這就是事實。 啊哈Reddit,沒了你我們還能在哪里從魚目混珠的網(wǎng)絡(luò)中提煉真正的精華?就在這雜亂無章的論壇中,的的確確存在著這樣一些精辟的討論。 比如有個叫Shambloroni的...
摘要:前言面試中對于技術(shù)職位,一般分筆試與面談,如果面試官的一些小問題你可以立馬找到對應(yīng)的知識點擴展開來,那么這就是你的優(yōu)勢,本系列將講述一些面試中的事,不會很詳細,但是應(yīng)該比較全面吧。 前言 面試中對于技術(shù)職位,一般分筆試與面談,如果面試官的一些小問題你可以立馬找到對應(yīng)的知識點擴展開來,那么這就是你的優(yōu)勢,本系列將講述一些java面試中的事,不會很詳細,但是應(yīng)該比較全面吧。 主要內(nèi)容 說到...
摘要:容易導(dǎo)致內(nèi)存泄漏。如果我們的強引用不存在的話,那么就會被回收,也就是會出現(xiàn)我們沒被回收,被回收,導(dǎo)致永遠存在,出現(xiàn)內(nèi)存泄漏。緩存行和一次定位,不會有沖突由于使用數(shù)組,不會出現(xiàn)回收,沒被回收的尷尬局面,所以避免了內(nèi)存泄漏。 1 背景 某一天在某一個群里面的某個群友突然提出了一個問題:threadlocal的key是虛引用,那么在threadlocal.get()的時候,發(fā)生GC之后,ke...
摘要:異常處理異常處理一直是回調(diào)的難題,而提供了非常方便的方法在一次調(diào)用中,任何的環(huán)節(jié)發(fā)生,都可以在最終的中捕獲到錯誤處理基本的小結(jié)具體的很多的用法可以參考阮一峰的入門教程,還有就是上面提到的電子書。 JS異步那些事 一 (基礎(chǔ)知識)JS異步那些事 二 (分布式事件)JS異步那些事 三 (Promise)JS異步那些事 四(HTML 5 Web Workers)JS異步那些事 五 (異步腳本...
閱讀 2107·2021-11-05 09:42
閱讀 2851·2021-09-23 11:21
閱讀 2841·2019-08-30 14:00
閱讀 3314·2019-08-30 13:15
閱讀 465·2019-08-29 17:18
閱讀 3547·2019-08-29 16:29
閱讀 2749·2019-08-29 14:06
閱讀 2794·2019-08-23 14:41