第2講 | Exception和Error有什么區(qū)別般码?

世界上存在永遠不會出錯的程序嗎?也許這只會出現(xiàn)在程序員的夢中乱顾。隨著編程語言和軟件的誕生板祝,異常情況就如影隨形地糾纏著我們,只有正確處理好意外情況走净,才能保證程序的可靠性券时。

Java語言在設(shè)計之初就提供了相對完善的異常處理機制,這也是Java得以大行其道的原因之一温技,因為這種機制大大降低了編寫和維護可靠程序的門檻革为。如今,異常處理機制已經(jīng)成為現(xiàn)代編程語言的標(biāo)配舵鳞。

今天我要問你的問題是震檩,請對比Exception和Error,另外蜓堕,運行時異常與一般異常有什么區(qū)別抛虏?

典型回答

Exception和Error都是繼承了Throwable類,在Java中只有Throwable類型的實例才可以被拋出(throw)或者捕獲(catch)套才,它是異常處理機制的基本組成類型迂猴。

Exception和Error體現(xiàn)了Java平臺設(shè)計者對不同異常情況的分類。Exception是程序正常運行中背伴,可以預(yù)料的意外情況沸毁,可能并且應(yīng)該被捕獲,進行相應(yīng)處理傻寂。

Error是指在正常情況下息尺,不大可能出現(xiàn)的情況,絕大部分的Error都會導(dǎo)致程序(比如JVM自身)處于非正常的疾掰、不可恢復(fù)狀態(tài)搂誉。既然是非正常情況,所以不便于也不需要捕獲静檬,常見的比如OutOfMemoryError之類炭懊,都是Error的子類并级。

Exception又分為可檢查(checked)異常和不檢查(unchecked)異常,可檢查異常在源代碼里必須顯式地進行捕獲處理侮腹,這是編譯期檢查的一部分嘲碧。前面我介紹的不可查的Error,是Throwable不是Exception凯旋。

不檢查異常就是所謂的運行時異常呀潭,類似 NullPointerException、ArrayIndexOutOfBoundsException之類至非,通常是可以編碼避免的邏輯錯誤钠署,具體根據(jù)需要來判斷是否需要捕獲,并不會在編譯期強制要求荒椭。

考點分析

分析Exception和Error的區(qū)別谐鼎,是從概念角度考察了Java處理機制∪せ荩總的來說狸棍,還處于理解的層面,面試者只要闡述清楚就好了味悄。

我們在日常編程中草戈,如何處理好異常是比較考驗功底的,我覺得需要掌握兩個方面侍瑟。

第一唐片,理解Throwable、Exception涨颜、Error的設(shè)計和分類费韭。比如,掌握那些應(yīng)用最為廣泛的子類庭瑰,以及如何自定義異常等星持。

很多面試官會進一步追問一些細節(jié),比如弹灭,你了解哪些Error督暂、Exception或者RuntimeException?我畫了一個簡單的類圖穷吮,并列出來典型例子逻翁,可以給你作為參考,至少做到基本心里有數(shù)酒来。

其中有些子類型,最好重點理解一下肪凛,比如NoClassDefFoundError和ClassNotFoundException有什么區(qū)別堰汉,這也是個經(jīng)典的入門題目辽社。

第二,理解Java語言中操作Throwable的元素和實踐翘鸭。掌握最基本的語法是必須的滴铅,如try-catch-finally塊,throw就乓、throws關(guān)鍵字等汉匙。與此同時,也要懂得如何處理典型場景生蚁。

異常處理代碼比較繁瑣噩翠,比如我們需要寫很多千篇一律的捕獲代碼,或者在finally里面做一些資源回收工作邦投。隨著Java語言的發(fā)展伤锚,引入了一些更加便利的特性,比如try-with-resources和multiple catch志衣,具體可以參考下面的代碼段屯援。在編譯時期,會自動生成相應(yīng)的處理邏輯念脯,比如狞洋,自動按照約定俗成close那些擴展了AutoCloseable或者Closeable的對象。

try (BufferedReader br = new BufferedReader(…);

? ? BufferedWriter writer = new BufferedWriter(…)) {// Try-with-resources

// do something

catch ( IOException | XEception e) {// Multiple catch

? // Handle it

}

知識擴展

前面談的大多是概念性的東西绿店,下面我來談些實踐中的選擇吉懊,我會結(jié)合一些代碼用例進行分析。

先開看第一個吧惯吕,下面的代碼反映了異常處理中哪些不當(dāng)之處惕它?

try {

? // 業(yè)務(wù)代碼

? // …

? Thread.sleep(1000L);

} catch (Exception e) {

? // Ignore it

}

這段代碼雖然很短,但是已經(jīng)違反了異常處理的兩個基本原則废登。

第一淹魄,盡量不要捕獲類似Exception這樣的通用異常,而是應(yīng)該捕獲特定異常堡距,在這里是Thread.sleep()拋出的InterruptedException甲锡。

這是因為在日常的開發(fā)和合作中,我們讀代碼的機會往往超過寫代碼羽戒,軟件工程是門協(xié)作的藝術(shù)缤沦,所以我們有義務(wù)讓自己的代碼能夠直觀地體現(xiàn)出盡量多的信息,而泛泛的Exception之類易稠,恰恰隱藏了我們的目的缸废。另外,我們也要保證程序不會捕獲到我們不希望捕獲的異常。比如企量,你可能更希望RuntimeException被擴散出來测萎,而不是被捕獲。

進一步講届巩,除非深思熟慮了硅瞧,否則不要捕獲Throwable或者Error,這樣很難保證我們能夠正確程序處理OutOfMemoryError恕汇。

第二腕唧,不要生吞(swallow)異常。這是異常處理中要特別注意的事情瘾英,因為很可能會導(dǎo)致非常難以診斷的詭異情況枣接。

生吞異常,往往是基于假設(shè)這段代碼可能不會發(fā)生方咆,或者感覺忽略異常是無所謂的月腋,但是千萬不要在產(chǎn)品代碼做這種假設(shè)!

如果我們不把異常拋出來瓣赂,或者也沒有輸出到日志(Logger)之類榆骚,程序可能在后續(xù)代碼以不可控的方式結(jié)束。沒人能夠輕易判斷究竟是哪里拋出了異常煌集,以及是什么原因產(chǎn)生了異常妓肢。

再來看看第二段代碼

try {

? // 業(yè)務(wù)代碼

? // …

} catch (IOException e) {

? ? e.printStackTrace();

}

這段代碼作為一段實驗代碼,它是沒有任何問題的苫纤,但是在產(chǎn)品代碼中碉钠,通常都不允許這樣處理。你先思考一下這是為什么呢卷拘?

我們先來看看printStackTrace()的文檔喊废,開頭就是“Prints this throwable and its backtrace to the?standard error stream”。問題就在這里栗弟,在稍微復(fù)雜一點的生產(chǎn)系統(tǒng)中污筷,標(biāo)準(zhǔn)出錯(STERR)不是個合適的輸出選項,因為你很難判斷出到底輸出到哪里去了乍赫。

尤其是對于分布式系統(tǒng)瓣蛀,如果發(fā)生異常,但是無法找到堆棧軌跡(stacktrace)雷厂,這純屬是為診斷設(shè)置障礙惋增。所以,最好使用產(chǎn)品日志改鲫,詳細地輸出到日志系統(tǒng)里诈皿。

我們接下來看下面的代碼段林束,體會一下Throw early, catch late原則

public void readPreferences(String fileName){

//...perform operations...

InputStream in = new FileInputStream(fileName);

//...read the preferences file...

}

如果fileName是null稽亏,那么程序就會拋出NullPointerException诊县,但是由于沒有第一時間暴露出問題,堆棧信息可能非常令人費解措左,往往需要相對復(fù)雜的定位。這個NPE只是作為例子避除,實際產(chǎn)品代碼中怎披,可能是各種情況,比如獲取配置失敗之類的瓶摆。在發(fā)現(xiàn)問題的時候凉逛,第一時間拋出,能夠更加清晰地反映問題群井。

我們可以修改一下状飞,讓問題“throw early”,對應(yīng)的異常信息就非常直觀了书斜。

public void readPreferences(String filename) {

Objects. requireNonNull(filename);

//...perform other operations...

InputStream in = new FileInputStream(filename);

//...read the preferences file...

}

至于“catch late”诬辈,其實是我們經(jīng)常苦惱的問題荐吉,捕獲異常后焙糟,需要怎么處理呢?最差的處理方式样屠,就是我前面提到的“生吞異炒┐椋”,本質(zhì)上其實是掩蓋問題痪欲。如果實在不知道如何處理悦穿,可以選擇保留原有異常的cause信息,直接再拋出或者構(gòu)建新的異常拋出去业踢。在更高層面栗柒,因為有了清晰的(業(yè)務(wù))邏輯,往往會更清楚合適的處理方式是什么陨亡。

有的時候傍衡,我們會根據(jù)需要自定義異常,這個時候除了保證提供足夠的信息负蠕,還有兩點需要考慮:

是否需要定義成Checked Exception蛙埂,因為這種類型設(shè)計的初衷更是為了從異常情況恢復(fù),作為異常設(shè)計者遮糖,我們往往有充足信息進行分類绣的。

在保證診斷信息足夠的同時,也要考慮避免包含敏感信息,因為那樣可能導(dǎo)致潛在的安全問題屡江。如果我們看Java的標(biāo)準(zhǔn)類庫芭概,你可能注意到類似java.net.ConnectException,出錯信息是類似“ Connection refused (Connection refused)”惩嘉,而不包含具體的機器名罢洲、IP、端口等文黎,一個重要考量就是信息安全惹苗。類似的情況在日志中也有,比如耸峭,用戶數(shù)據(jù)一般是不可以輸出到日志里面的桩蓉。

業(yè)界有一種爭論(甚至可以算是某種程度的共識),Java語言的Checked Exception也許是個設(shè)計錯誤劳闹,反對者列舉了幾點:

Checked Exception的假設(shè)是我們捕獲了異常院究,然后恢復(fù)程序。但是本涕,其實我們大多數(shù)情況下业汰,根本就不可能恢復(fù)。Checked Exception的使用菩颖,已經(jīng)大大偏離了最初的設(shè)計目的蔬胯。

Checked Exception不兼容functional編程,如果你寫過Lambda/Stream代碼位他,相信深有體會氛濒。

很多開源項目,已經(jīng)采納了這種實踐鹅髓,比如Spring舞竿、Hibernate等,甚至反映在新的編程語言設(shè)計中窿冯,比如Scala等骗奖。 如果有興趣,你可以參考:

http://literatejava.com/exceptions/checked-exceptions-javas-biggest-mistake/醒串。

當(dāng)然执桌,很多人也覺得沒有必要矯枉過正,因為確實有一些異常芜赌,比如和環(huán)境相關(guān)的IO仰挣、網(wǎng)絡(luò)等,其實是存在可恢復(fù)性的缠沈,而且Java已經(jīng)通過業(yè)界的海量實踐膘壶,證明了其構(gòu)建高質(zhì)量軟件的能力错蝴。我就不再進一步解讀了,感興趣的同學(xué)可以點擊鏈接颓芭,觀看Bruce Eckel在2018年全球軟件開發(fā)大會QCon的分享Failing at Failing: How and Why We’ve Been Nonchalantly Moving Away From Exception Handling顷锰。

我們從性能角度來審視一下Java的異常處理機制,這里有兩個可能會相對昂貴的地方:

try-catch代碼段會產(chǎn)生額外的性能開銷亡问,或者換個角度說官紫,它往往會影響JVM對代碼進行優(yōu)化,所以建議僅捕獲有必要的代碼段州藕,盡量不要一個大的try包住整段的代碼万矾;與此同時,利用異成骺颍控制代碼流程,也不是一個好主意后添,遠比我們通常意義上的條件語句(if/else笨枯、switch)要低效。

Java每實例化一個Exception遇西,都會對當(dāng)時的棧進行快照馅精,這是一個相對比較重的操作。如果發(fā)生的非常頻繁粱檀,這個開銷可就不能被忽略了洲敢。

所以,對于部分追求極致性能的底層類庫茄蚯,有種方式是嘗試創(chuàng)建不進行椦古恚快照的Exception。這本身也存在爭議渗常,因為這樣做的假設(shè)在于壮不,我創(chuàng)建異常時知道未來是否需要堆棧。問題是皱碘,實際上可能嗎询一?小范圍或許可能,但是在大規(guī)模項目中癌椿,這么做可能不是個理智的選擇健蕊。如果需要堆棧,但又沒有收集這些信息踢俄,在復(fù)雜情況下缩功,尤其是類似微服務(wù)這種分布式系統(tǒng),這會大大增加診斷的難度都办。

當(dāng)我們的服務(wù)出現(xiàn)反應(yīng)變慢掂之、吞吐量下降的時候抗俄,檢查發(fā)生最頻繁的Exception也是一種思路。關(guān)于診斷后臺變慢的問題世舰,我會在后面的Java性能基礎(chǔ)模塊中系統(tǒng)探討动雹。

今天,我從一個常見的異常處理概念問題跟压,簡單總結(jié)了Java異常處理的機制胰蝠。并結(jié)合代碼,分析了一些普遍認可的最佳實踐震蒋,以及業(yè)界最新的一些異常使用共識茸塞。最后,我分析了異常性能開銷查剖,希望對你有所幫助钾虐。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市笋庄,隨后出現(xiàn)的幾起案子效扫,更是在濱河造成了極大的恐慌,老刑警劉巖直砂,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菌仁,死亡現(xiàn)場離奇詭異,居然都是意外死亡静暂,警方通過查閱死者的電腦和手機济丘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洽蛀,“玉大人摹迷,你說我怎么就攤上這事〗脊” “怎么了泪掀?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長颂碘。 經(jīng)常有香客問我异赫,道長,這世上最難降的妖魔是什么头岔? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任塔拳,我火速辦了婚禮,結(jié)果婚禮上峡竣,老公的妹妹穿的比我還像新娘靠抑。我一直安慰自己,他們只是感情好适掰,可當(dāng)我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布颂碧。 她就那樣靜靜地躺著荠列,像睡著了一般。 火紅的嫁衣襯著肌膚如雪载城。 梳的紋絲不亂的頭發(fā)上肌似,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天,我揣著相機與錄音诉瓦,去河邊找鬼川队。 笑死,一個胖子當(dāng)著我的面吹牛睬澡,可吹牛的內(nèi)容都是我干的固额。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼煞聪,長吁一口氣:“原來是場噩夢啊……” “哼斗躏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起昔脯,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤啄糙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后栅干,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡捐祠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年碱鳞,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片踱蛀。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡窿给,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出率拒,到底是詐尸還是另有隱情崩泡,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布猬膨,位于F島的核電站角撞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏勃痴。R本人自食惡果不足惜谒所,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沛申。 院中可真熱鬧劣领,春花似錦、人聲如沸铁材。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至村生,卻和暖如春惊暴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梆造。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工缴守, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人镇辉。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓屡穗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親忽肛。 傳聞我的和親對象是個殘疾皇子村砂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容