Java 語言在設(shè)計之初就提供了相對完善的異常處理機(jī)制,這種機(jī)制大大降低了編寫和維護(hù)可靠程序的門檻湾宙。
Exception 和 Error 都是繼承了 Throwable 類,在 Java 中只有 Throwable 類型的實(shí)例才可以被拋出(throw)或者捕獲(catch)冈绊,它是異常處理機(jī)制的基本組成類型侠鳄。
Exception 和 Error 體現(xiàn)了 Java 平臺設(shè)計者對不同異常情況的分類。
Exception 是程序正常運(yùn)行中焚碌,可以預(yù)料的意外情況畦攘,可能并且應(yīng)該被捕獲,進(jìn)行相應(yīng)處理十电。Error 是指在正常情況下知押,不大可能出現(xiàn)的情況,絕大部分的 Error 都會導(dǎo)致程序(比如 JVM 自身)處于非正常的鹃骂、不可恢復(fù)狀態(tài)台盯。既然是非正常情況,所以不便于也不需要捕獲畏线,常見的比如 OutOfMemoryError 之類静盅,都是 Error 的子類。
Exception 又分為 checked 異常和 unchecked 異常寝殴,checked 異常在源代碼里必須顯式地進(jìn)行捕獲處理蒿叠,這是編譯期檢查的一部分,比如說 IOException蚣常。unchecked 異常就是所謂的運(yùn)行時異常市咽,類似 NullPointerException、ArrayIndexOutOfBoundsException 之類抵蚊,通常是可以編碼避免的邏輯錯誤施绎,具體根據(jù)需要來判斷是否需要捕獲,并不會在編譯期強(qiáng)制要求贞绳。
異常處理代碼比較繁瑣谷醉,比如我們需要寫很多千篇一律的捕獲代碼,或者在 finally 里面做一些資源回收工作冈闭。隨著 Java 語言的發(fā)展俱尼,引入了一些更加便利的特性,比如 try-with-resources 和 multiple catch萎攒,具體可以參考下面的代碼段号显。在編譯時期臭猜,會自動生成相應(yīng)的處理邏輯,比如押蚤,自動按照約定俗成 close 那些擴(kuò)展了 AutoCloseable 或者 Closeable 的對象蔑歌。
try (BufferedReader br = new BufferedReader(…);
BufferedWriter writer = new BufferedWriter(…)) {// Try-with-resources
// do something
catch ( IOException | XEception e) {// Multiple catch
// Handle it
}
盡量不要捕獲類似 Exception 這樣的通用異常,而是應(yīng)該捕獲特定異常揽碘。盡量不要捕獲 Throwable 或者 Error次屠,這樣很難保證我們能夠正確程序處理 OutOfMemoryError。
不要使用 e.printStackTrace() 的方法處理異常雳刺,這種方式在生產(chǎn)環(huán)境中不方便定位問題劫灶。最好使用產(chǎn)品日志,詳細(xì)地輸出到日志系統(tǒng)里掖桦。
在保證診斷信息足夠的同時本昏,也要考慮避免包含敏感信息,因為那樣可能導(dǎo)致潛在的安全問題枪汪。如果我們看 Java 的標(biāo)準(zhǔn)類庫涌穆,你可能注意到類似 java.net.ConnectException,出錯信息是類似“ Connection refused (Connection refused)”雀久,而不包含具體的機(jī)器名宿稀、IP、端口等赖捌,一個重要考量就是信息安全祝沸。類似的情況在日志中也有,比如越庇,用戶數(shù)據(jù)一般是不可以輸出到日志里面的罩锐。
Java 類庫中定義的一類 RuntimeException 可以通過預(yù)先檢查進(jìn)行規(guī)避,而不應(yīng)該通過catch 來處理卤唉,比如: IndexOutOfBoundsException涩惑,NullPointerException 等等。
對大段代碼進(jìn)行 try-catch搬味,這是不負(fù)責(zé)任的表現(xiàn)境氢。catch 時請分清穩(wěn)定代碼和非穩(wěn)定代碼蟀拷,穩(wěn)定代碼指的是無論如何不會出錯的代碼碰纬。對于非穩(wěn)定代碼的 catch 盡可能進(jìn)行區(qū)分異常類型,再做對應(yīng)的異常處理问芬。
捕獲異常是為了處理它悦析,不要捕獲了卻什么都不處理而拋棄之。
我們從性能角度來審視一下 Java 的異常處理機(jī)制此衅,這里有兩個可能會相對昂貴的地方:
- try-catch 代碼段會產(chǎn)生額外的性能開銷强戴,或者換個角度說亭螟,它往往會影響 JVM 對代碼進(jìn)行優(yōu)化,所以建議僅捕獲有必要的代碼段骑歹,盡量不要一個大的 try 包住整段的代碼预烙;與此同時,利用異车烂模控制代碼流程扁掸,也不是一個好主意,遠(yuǎn)比我們通常意義上的條件語句(if/else最域、switch)要低效谴分。
- Java 每實(shí)例化一個 Exception,都會對當(dāng)時的棧進(jìn)行快照镀脂,這是一個相對比較重的操作牺蹄。如果發(fā)生的非常頻繁,這個開銷可就不能被忽略了薄翅。