世界上并不存在不會出錯的系統(tǒng)萍聊,只要是軟件系統(tǒng)就一定會在運(yùn)行的過程中出現(xiàn)開發(fā)人員無法預(yù)料的錯誤惩淳。如何處理意外發(fā)生就是我們作為一名開發(fā)人員所必須深入思考的問題惫撰。
Java語言提供了完善的異常處理機(jī)制然眼,它有效的降低了編寫以及維護(hù)的門檻(這也是Java語言大行其道的原因甘邀,好上手,機(jī)制全)遂蛀。今天在這里和大家分享一下Java異常機(jī)制的特點(diǎn)以及應(yīng)用谭跨。
現(xiàn)在我們回想一下干厚,在日常開發(fā)中我們常常用到try……catch……語句李滴,比如寫一個文件輸入輸出流,此時我們必須catch相應(yīng)的IOException蛮瞄、FileNotFountException所坯,否則編譯不通過,這里catch的是什么東西挂捅?答案是“Exception”芹助。平時我們在系統(tǒng)運(yùn)行中發(fā)現(xiàn)系統(tǒng)崩潰,比如OutOfMemoryError錯誤闲先,這個OOM是什么東西状土?答案是“Error”。綜上所述伺糠,Java中的異常分為兩大類蒙谓,Exception和Error,它們都繼承了Throwable類(只有集成了Throwable類才能被拋出或者捕獲)训桶。
一累驮、Exception與Error
Error是指正常情況下不大可能出現(xiàn)的情況酣倾,一旦出現(xiàn)很有可能導(dǎo)致程序處于不可恢復(fù)的非正常狀態(tài),開發(fā)人員不便于也不需要捕獲谤专。Exception則可以分為checked和unchecked異常躁锡,checked表示必須顯示捕獲處理,否則編譯不通過置侍。
Object | Object |
---|---|
Throwable | Throwable |
Error | Exception |
LinkageError映之、VirtualMachineError | unchecked、checked |
NoClassDefFoundError和ClassNotFoundException有什么區(qū)別蜡坊?
NoClassDefFoundError是一個錯誤(Error)惕医,而ClassNotFoundException是一個異常(Exception),我們應(yīng)該嘗試從異常中恢復(fù)程序算色,而不應(yīng)該嘗試從錯誤中恢復(fù)程序抬伺。
ClassNotFoundException
ClassNotFoundException是一個checkedException,需要顯示捕獲處理灾梦。
ClassNotFoundException產(chǎn)生的原因是:Java使用Class.forName方法動態(tài)加載類到JVM內(nèi)存中峡钓,但是如果傳遞的類在類路徑中沒有被找到,那么就會拋出ClassNotFoundException異常若河。ClassLoader.loadClass能岩,ClassLoader.findSystemClass等方法在動態(tài)加載類到內(nèi)存中的時候也可能會跑出去這個異常。
還有一種導(dǎo)致ClassNotFoundException發(fā)生的原因萧福,當(dāng)一個類已經(jīng)被某個類加載器加載到內(nèi)存中了拉鹃,此時另一個類加載器又嘗試動態(tài)從同一個包中加載這個類。此時也會出現(xiàn)ClassNotFoundException異常鲫忍。
Demo:
public class Example {
public static void main(String args[]) {
try
{
Class.forName("GeeksForGeeks");
}
catch (ClassNotFoundException ex)
{
ex.printStackTrace();
}
}
}
NoClassDefFoundError
NoClassDefFoundError產(chǎn)生的原因是:JVM或者ClassLoader實(shí)例嘗試加載類的時候找不到類的定義膏燕。它是一個LlinkageError,LinkageError發(fā)生的情況是在一個類依賴另一個類悟民,而后者在前者編譯后又發(fā)生了改變坝辫。導(dǎo)致出現(xiàn)LinkageError的錯誤。
Demo:
class GeeksForGeeks
{
void greeting()
{
System.out.println("hello!");
}
}
class G4G {
public static void main(String args[])
{
GeeksForGeeks geeks = new GeeksForGeeks();
geeks.greeting();
}
}
分別使用javac編譯兩個文件射亏,然后使用java G4G運(yùn)行近忙。當(dāng)我們把編譯后的GeeksForGeeks.class文件拿走后,就會報(bào)NoClassDefFoundError的錯誤智润。
二及舍、異常處理原則
通過上面的描述我們理解了Error和Exception之間的區(qū)別,接下來我們需要理解Java語言是如何操作Throwable元素窟绷【饴辏基本的語法包括“try-catch-finally”,“throw”钾麸,“throws”更振,“try-with-resources”等炕桨。異常處理有幾個原則需要遵守:
(1)盡量不要捕獲類似Exception這樣的通用異常,而應(yīng)該捕獲特定異常肯腕。
(2)不要生吞異常献宫。注意不要在生產(chǎn)環(huán)境使用e.printStackTrace()打印異常,最好使用產(chǎn)品日志实撒,詳細(xì)輸出到日志系統(tǒng)姊途。
(3)Throw early, catch late。提前把異常拋出來知态,或者構(gòu)建新的異常拋出去捷兰。這里就涉及到自定義異常,需要確定是否需要自定義checked Exception负敏;在保證診斷信息的同事避免敏感信息贡茅,比如java.net.ConnectException的出錯信息不包括機(jī)器名、IP其做、端口等敏感信息(日志信息也一樣顶考,不可以輸出用戶信息之類的敏感信息)。
有一種說法妖泄,提出Java語言的checkedException是一種設(shè)計(jì)錯誤驹沿。因?yàn)閏heckedException的初衷是希望捕獲異常,再從異常中回復(fù)正常蹈胡,但是大多數(shù)情況不能恢復(fù)渊季。另外checkedException不兼容functional編程(函數(shù)式編程,后續(xù)我會展開來講函數(shù)式編程罚渐、命令式編程的區(qū)別)述召,比如Lambda/Stream代碼(后續(xù)會詳細(xì)講Lambda的語法以及原理)居灯。但也有一部分人提出娶吞,確實(shí)有一些異常是可恢復(fù)的骡澈,比如IO異常冯事、網(wǎng)絡(luò)異常等败潦。
三嗡贺、異常處理與性能
處理異常會必然會增加代碼量讶隐,那么接下來我們從性能的角度來審視Java的異常處理機(jī)制赎懦。
- try-catch代碼會影響JVM對代碼的優(yōu)化,所以不要用try包裹大段的代碼。
- 不要試圖用異常捕獲來控制代碼流程态罪,比起if/else之類的語句地消,異常捕獲要更加低效。
- Java實(shí)例化Exception都會對當(dāng)時的棧進(jìn)行快照当悔,該操作較重傅瞻。對于追求極致性能的底層類庫踢代,創(chuàng)建不進(jìn)行棧快照的Exception是一種方法嗅骄。但是這樣不利于我們定位問題胳挎,特別是在微服務(wù)這樣的分布式系統(tǒng),會增加診斷的難度溺森。當(dāng)服務(wù)變慢慕爬,吞度量下降時,檢查最頻繁的Exception是一種思路屏积。
四医窿、總結(jié)
通過本章的學(xué)習(xí),我們了解了Java異常中的兩大類:Exception與Error炊林。另外我們也學(xué)習(xí)了ClassNotFoundException和NoClassDefFoundError之間的區(qū)別姥卢。Java異常處理機(jī)制是Java語言一大特征,遵循異常處理原則可以有效提高代碼可讀性以及程序運(yùn)行的穩(wěn)定性渣聚。深入的掌握J(rèn)ava異常處理機(jī)制對我們Debug線上問題也有幫助隔显。
五、后記
回顧問題:
- Exception和Error有什么相同點(diǎn)饵逐、不通點(diǎn)括眠?
- 常見的CheckedException和RuntimeException有哪些?常見的Error有哪些倍权?
- NoClassDefFoundError和ClassNotFoundException有什么區(qū)別掷豺?
延伸問題:
- 為什么錯誤信息打印,不推薦使用e.printStackTrace()薄声?STERR為什么不是個合適的輸出項(xiàng)当船?
- 為什么使用try-catch來控制代碼流程,比起if-else更加低效默辨?
- 如何創(chuàng)建不進(jìn)行椀缕担快照的Exception?