Java的異常都是派生于Throwable類的一個實例敌厘,所有的異常都是由Throwable繼承而來的滴须。Throwable有分為了Error類和Exception類舌狗。
Error(錯誤)
Error 類層次結(jié)構(gòu)描述了 Java 運行時系統(tǒng)的內(nèi)部錯誤和資源耗盡錯誤。Error表示比較嚴重的問題扔水,一般是JVM運行時出現(xiàn)了錯誤痛侍,如沒有內(nèi)存可分配拋出OOM錯誤、棧資源耗盡拋出StackOverflowError錯誤魔市、Java虛擬機運行錯誤Virtual MachineError主届、類定義錯誤NoClassDefFoundError。如果出現(xiàn)了這樣的內(nèi)部錯誤待德, 除了通告給用戶君丁,并盡力使程序安全地終止之外, 再也無能為力了将宪。
Exception(異常)
異常又分為RuntimeException和其他異常绘闷。由程序錯誤導致的異常屬于RuntimeException橡庞,而程序本身沒有問題,但由于像 I/O 錯誤這類問題導致的異常屬于其他異常印蔗。
運行時異常RuntimeException:顧名思義扒最,運行時才可能拋出的異常,編譯器不會處理此類異常华嘹。比如數(shù)組索引越界吧趣、使用的對象為空、強制類型轉(zhuǎn)換錯誤除呵、除0等等再菊。出現(xiàn)了運行時異常,一般是程序的邏輯有問題颜曾,是程序自身的問題而非外部因素纠拔。
其他異常:Exception中除了運行時異常之外的,都屬于其他異常泛豪。也可以稱之為編譯時異常稠诲,這部分異常編譯器要求必須處置。這部分異常常常是因為外部運行環(huán)境導致诡曙,因為程序可能運行在各種環(huán)境中臀叙,如打開一個不存在的文件,此時拋出FileNotFoundException价卤。編譯器要求Java程序必須捕獲或聲明所有的編譯時異常劝萤,強制要求程序為可能出現(xiàn)的異常做準備工作。
不要被運行時異常的名稱所迷惑慎璧,理論上所有的錯誤都是運行時發(fā)生的床嫌。包括Error、RuntimeException胸私、編譯時異常等等厌处。所有的這些都只能在程序運行的過程中才能碰到。編譯時異常指的是編譯器要求必須處理的異常岁疼,并不是代碼編譯間發(fā)生的錯誤阔涉。
受檢異常和非受檢異常
字面理解,接受檢查的異常和不接受檢查的異常捷绒。根據(jù)上面的信息瑰排,Error和RuntimeException運行時異常都不能被檢查。他們都是程序運行過程中所產(chǎn)生的疙驾,只是Error的錯誤比較嚴重凶伙,一般是JVM產(chǎn)生,而RuntimeException一般是程序邏輯自身的問題它碎。受檢異常就是上面的編譯時異常函荣,這些異常在編譯時被強制要求捕獲或者聲明显押。編譯器將會為所有的受檢異常提供異常處理器。
PS:其實異常發(fā)生了傻挂,除了更改程序或者配置等乘碑,沒有其他的方法。只是對引起程序不正常工作的原因進行分類金拒,就出現(xiàn)了Error和Exception(運行時異常和編譯時異常)兽肤。這種分類能讓程序員更好的定位因此錯誤的原因,更方便更高效的進行開發(fā)绪抛,寫出健壯性更好的代碼资铡。但是異常并不能改變當前運行的結(jié)果,因為從程序開始運行的那一刻幢码,整個邏輯和數(shù)據(jù)都已經(jīng)固定了笤休。
異常處理機制
主要是try-catch-finally和throw、throws關鍵字症副。
異常都是派生于Throwable類的一個實例店雅,這個實例可以由JVM產(chǎn)生,也可以在程序中手動創(chuàng)建贞铣,用throw手動拋出闹啦。throw的對象必須是派生于Throwable類的實例,其他類型無法通過編譯辕坝。
受檢異常只有兩種選擇窍奋。要么被捕獲處理,要么被拋出酱畅,讓調(diào)用者處理费变。而非受檢異常沒有此強制要求。
throws是用來聲明異常圣贸,只要是派生于Throwable類的都可以被聲明。方法應該在其首部聲明所有可能拋出的受檢異常扛稽,這是強制要求的吁峻。非受檢異常不要求必須通過throws聲明,因為Error發(fā)生后對其無能為力在张,而如果有運行時異常用含,那么就是程序自身的問題,應該把時間花在修正程序的錯誤上帮匾,而不是說明程序發(fā)生的可能性上啄骇。所以編寫程序的時候throws關注點是受檢異常,但是非受檢異常也可以通過throws聲明瘟斜,只是不強制要求而已缸夹。若有多個受檢異常痪寻,必須在throws中全部聲明,如果方法沒有聲明所有可能的受檢異常虽惭,編譯器就會發(fā)出錯誤提示橡类。
try-catch-finally用來捕獲異常。所有派生于Throwable類都可以通過catch捕獲芽唇,try中放可能存在異常的方法顾画,如果在 try語句塊中的任何代碼拋出了一個在 catch 子句中說明的異常類,那么程序?qū)⑻^ try語句塊的其余代碼匆笤,并且執(zhí)行 catch 子句中的處理器代碼研侣。如果在 try 語句塊中的代碼沒有拋出任何異常,那么程序?qū)⑻^ catch 子句炮捧,如果方法中的任何代碼拋出了一個在 catch 子句中沒有聲明的異常類型庶诡,那么這個方法就會立刻退出。
在一個 try 語句塊中可以捕獲多個異常類型寓盗,并對不同類型的異常做出不同的處理灌砖。可以為每個異常類型使用一個單獨的 catch 子句傀蚌。需要注意基显,如果多個catch中的異常非繼承關系,那么catch順序不影響結(jié)果善炫,如果catch異常存在類繼承關系撩幽,那么子類的catch應該放在前面,父類的在后面箩艺。如下圖窜醉,catch的判斷是從上到下,如果父類在前艺谆,那么派生于父類的都會被捕獲榨惰,執(zhí)行父類的處理代碼爷狈,這樣導致后面子類的處理代碼永遠不會被執(zhí)行板熊。
finally一般用來關閉所占用的資源猛频。如果代碼拋出異常坝撑,就會終止剩余代碼的處理博投,并且退出這個方法交煞。這樣可能會導致一些程序占用的系統(tǒng)并不能被正確的釋放纺座。而不管是否有異常被捕獲硝枉,finally中子句的代碼都會被執(zhí)行抹估,可以在這里正確的釋放資源缠黍。
抓拋模型
所有的異常,一定是先有一個實例然后拋出药蜻。這個可以JVM完成瓷式,也可以手動執(zhí)行替饿。這個實例是整個異常處理的源頭。然后調(diào)用類可以利用try-catch-finally對異常進行捕獲蒿往,也可以在方法首部通過throws繼續(xù)向上層拋出盛垦。try-catch-finally是抓,throw/throws是拋瓤漏。一個類遇到異常腾夯,要么捕獲后處理,要么繼續(xù)向上拋出蔬充,讓調(diào)用者進行處理蝶俱。
catch中可以為空,這樣程序就會忽略掉哪些異常饥漫。不進行處理本身就是一種處理方式榨呆。在catch中也可能拋出異常,也可以手動拋出庸队。這樣可以改變異常的類型积蜻,使用異常的包裝技術(shù),原先的異常時新異常產(chǎn)生的原因彻消,可以讓用戶拋出子系統(tǒng)中的高級異常竿拆,而不會丟失原始異常的細節(jié)。
try-catch-finally可以靈活組合宾尚”瘢可以try-catch、try-finally或者try-catch-finally煌贴∮澹可以分為以下幾種情況
1.try中執(zhí)行正常,忽略catch牛郑,最后執(zhí)行finally怠肋。
2.try中出現(xiàn)異常,且異常在catch中聲明淹朋,執(zhí)行catch,最后執(zhí)行finally.
3.try中出現(xiàn)異常灶似,但catch中未聲明,不執(zhí)行catch瑞你,最后執(zhí)行finally.
4.try中出現(xiàn)異常,且異常在catch中聲明希痴,執(zhí)行catch時出現(xiàn)異常者甲,停止catch中代碼,執(zhí)行finally并且拋出catch中新出現(xiàn)的異常砌创。
finally
finally不管是try或者catch中沒有由異常虏缸,最后大概率都會執(zhí)行鲫懒。這是因為編譯器會講 finally 塊中的代碼復制兩份并分別添加在 try 和 catch 的后面。但是如果try或者catch中出現(xiàn)了 System.exit()語句刽辙,則會直接退出窥岩,并不會執(zhí)行finally模塊。
finally中沒有return語句 最后返回值為2
finally中有return語句 最后返回值為3
上面兩個代碼只是在finally中是否存在return語句的區(qū)別宰缤,但直接結(jié)果卻并不相同颂翼。可以看到如果finally中沒有return語句慨灭,程序就會把finally中的操縱數(shù)據(jù)忽略掉朦乏。其實finally中的數(shù)據(jù)操作也是執(zhí)行了的,但是并沒有返回氧骤。這是因為在return語句返回之前呻疹,虛擬機會將待返回的值壓入操作數(shù)棧,等待返回筹陵。即使 finally 語句塊對 i 進行了修改刽锤,但是待返回的值已經(jīng)確實的存在于操作數(shù)棧中了,所以不會影響程序返回結(jié)果朦佩。
在try中的數(shù)據(jù)處理完并思,檢測到有return語句時,會先將數(shù)據(jù)壓入操作數(shù)棧等待返回吕粗,然后去執(zhí)行finally語句纺荧,如果finally語句沒有將新的結(jié)果壓入操作數(shù)棧,那么只可能返回原先的結(jié)果颅筋。從這個角度理解宙暇,即使finally中對數(shù)據(jù)處理,但是返回的依舊時try中的“臟數(shù)據(jù)”议泵。finally并不是沒有執(zhí)行占贫,而是執(zhí)行了卻沒有沒返回。添加return語句則會將finally處理過的數(shù)據(jù)壓入操作數(shù)棧返回先口,原先的“臟數(shù)據(jù)”失效型奥。
注意這里指的是基本變量,如果是引用類型不受此影響碉京。因為不管在哪里進行運算厢汹,處理的都是引用背后的“實體”。
Throwable
Throwable是頂層的異常類谐宙,下面是 Throwable 類的主要方法:
1.public String getMessage()
返回關于發(fā)生的異常的詳細信息烫葬。這個消息在Throwable 類的構(gòu)造函數(shù)中初始化了
2.public Throwable getCause()
返回一個Throwable 對象代表異常原因
3.public String toString()
使用getMessage()的結(jié)果返回類的串級名字
4.public void printStackTrace()
打印toString()結(jié)果和棧層次到System.err,即錯誤輸出流
5.public StackTraceElement [] getStackTrace()
返回一個包含堆棧層次的數(shù)組。下標為0的元素代表棧頂搭综,最后一個元素代表方法調(diào)用堆棧的棧底
6.public Throwable fillInStackTrace()
用當前的調(diào)用棧層次填充Throwable 對象棧層次垢箕,添加到棧層次任何先前信息中
自定義異常
Java 的異常機制中所定義的所有異常不可能預見所有可能出現(xiàn)的錯誤,某些特定的情境下兑巾,則需要我們自定義異常類型來向上報告某些錯誤信息条获。
一般地,用戶自定義異常類都是RuntimeException的子類
自定義異常類通常需要編寫幾個重載的構(gòu)造器
自定義異常需要提供 serialVersionUID 序列化唯一ID蒋歌,方便調(diào)試
自定義異常最重要的是異常類的名字帅掘,當異常出現(xiàn)時,可以根據(jù) 名字判斷異常類型
注意事項
當子類重寫父類帶有throws聲明的函數(shù)時奋姿,其聲明的異常范圍必須要在父類的支持范圍內(nèi)锄开,即范圍不能比父類大。只能保持范圍不變或者更精確称诗,不能變大萍悴。如果父類沒有throws,那么子類也不能有throws寓免,受檢異常必須在子類內(nèi)部捕獲處理癣诱。
Java程序可以是多線程的。每一個線程都是一個獨立的執(zhí)行流袜香,獨立的函數(shù)調(diào)用棧撕予。如果程序只有一個線程,那么沒有被任何代碼處理的異常 會導致程序終止蜈首。如果是多線程的实抡,那么沒有被任何代碼處理的異常僅僅會導致異常所在的線程結(jié)束。也就是說欢策,Java中的異常是線程獨立的吆寨,線程的問題應該由線程自己來解決,而不要委托到外部踩寇,也不會直接影響到其它線程的執(zhí)行啄清。
有興趣了解更多編程基礎知識可以觀看教學視頻繼續(xù)學習。異常處理不能代替簡單的測試俺孙。捕獲異常所花費的時間要遠遠的超過了測試的時間辣卒,所以在可能的情況下,先對執(zhí)行條件進行判斷睛榄,而不要等執(zhí)行出錯之后捕獲異常荣茫。