Java中Exception和Error的區(qū)別詳解
世界上存在永遠(yuǎn)不會(huì)出錯(cuò)的程序嗎?也許這只會(huì)出現(xiàn)在程序員的夢(mèng)中。隨著編程語(yǔ)言和軟件的誕生,異常情況就如影隨形地糾纏著我們,只有正確的處理好意外情況,才能保證程序的可靠性。
java語(yǔ)言在設(shè)計(jì)之初就提供了相對(duì)完善的異常處理機(jī)制,這也是java得以大行其道的原因之一,因?yàn)檫@種機(jī)制大大降低了編寫和維護(hù)可靠程序的門檻。如今,異常處理機(jī)制已經(jīng)成為現(xiàn)代編程語(yǔ)言的標(biāo)配。
今天我要問(wèn)你的問(wèn)題是,請(qǐng)對(duì)比Exception和Error,另外,運(yùn)行時(shí)異常與一般異常有什么區(qū)別?
典型回答
Exception和Error都是繼承了Throwable類,在java中只有Throwable類型的實(shí)例才可以被拋出(throw)或者捕獲(catch),他是異常處理機(jī)制的基本組成類型。
Exception和Error體現(xiàn)了java平臺(tái)設(shè)計(jì)者對(duì)不同異常情況的分類,Exception是程序正常運(yùn)行中,可以預(yù)料的意外情況,可能并且應(yīng)該被捕獲,進(jìn)行相應(yīng)的處理。
Error是指正常情況下,不大可能出現(xiàn)的情況,絕大部分的Error都會(huì)導(dǎo)致程序(比如JVM自身)處于非正常狀態(tài),不可恢復(fù)狀態(tài)。既然是非正常情況,所以不便于也不需要捕獲,常見(jiàn)的比如OutOfMemoryError之類,都是Error的子類。
Exception又分為可檢查(checked)異常和不檢查(unchecked)異常,可檢查異常在源碼里必須顯示的進(jìn)行捕獲處理,這里是編譯期檢查的一部分。前面我們介紹的不可查的Error,是Throwable不是Exception。
不檢查異常就是所謂的運(yùn)行時(shí)異常,類似NullPointerException,ArrayIndexOutOfBoundsExceptin之類,通常是可以編碼避免的邏輯錯(cuò)誤,具體根據(jù)需要來(lái)判斷是否需要捕獲,并不會(huì)在編譯器強(qiáng)制要求。
考點(diǎn)分析:
分析Exception和Error的區(qū)別,是從概念角度考察了java處理機(jī)制。總的來(lái)說(shuō),還處于理解的層面,面試者只要闡述清楚就好了。
我們?cè)谌粘5木幊讨?,如何處理好異常是比較考驗(yàn)功底的。我覺(jué)得需要掌握兩個(gè)方面:
第一,理解Throwable,Exception,Error的設(shè)計(jì)和分類。比如,掌握那些應(yīng)用最為廣泛的子類,以及如何自定義異常等。
很多面試官會(huì)進(jìn)一步追問(wèn)一些細(xì)節(jié),比如,你了解哪些Error,Exception或者RuntimeException?我畫(huà)了一個(gè)簡(jiǎn)單的類圖,并列出來(lái)典型例子,可以給你作為參考,至少做到基本心里有數(shù)。
其中有些子類型,最好重點(diǎn)理解一下,比如
NoClassDefFoundError和ClassNotFoundException有什么區(qū)別,這也是一個(gè)經(jīng)典的入門題目。
簡(jiǎn)單總結(jié)就是,NoClassDefFoundError發(fā)生在編譯時(shí)對(duì)應(yīng)的類可用,而運(yùn)行時(shí)在Java的classpath路徑中,對(duì)應(yīng)的類不可用導(dǎo)致的錯(cuò)誤。
第二,理解java語(yǔ)言中操作Throwable的元素和實(shí)踐。掌握最基本的語(yǔ)法是必須的,比如try-catch-finally塊,throw,throws關(guān)鍵字等。與此同時(shí),也要懂得如何處理典型場(chǎng)景。
異常處理代碼比較繁瑣,比如我們需要寫很多千篇一律的捕獲代碼,或者在finally里面做一些資源回收工作。隨著java語(yǔ)言的發(fā)展,引入了一些更加便利的特性,比如try-with-resource和multiple catch,具體可以參考小面的代碼段,在編譯時(shí)期,會(huì)自動(dòng)生成相應(yīng)的處理邏輯,比如,自動(dòng)按照約定俗成close那些擴(kuò)展了AutoCloseable或者Closeable的對(duì)象。
try(BuffereReader br =new BufferReader(...); BufferedWriter writer = new BufferredWriter(...)) {//dosomething} catch(IOException | XException){//handle it}
知識(shí)擴(kuò)展
前面談的大多是概念性的東西,下面我來(lái)談一些實(shí)踐中的選擇,我會(huì)結(jié)合一些代碼用例進(jìn)行分析。
先看第一個(gè),下面的代碼反應(yīng)了異常處理中哪些不當(dāng)之處?
try{ //業(yè)務(wù)代碼 Thread.sleep(1000L); }catch(Exception e){ //Ignore it }
這段代碼雖然很短,但是異常違反了異常處理的兩個(gè)基本原則。
第一,盡量不要捕獲類似Exception這樣的通用異常,而是應(yīng)該捕獲特定異常,這里是Thread。sleep()拋出的InterruptedException.
這是因?yàn)樵谌粘5拈_(kāi)發(fā)合作中,我們讀代碼的機(jī)會(huì)往往超過(guò)寫代碼,軟件工程師門協(xié)作的藝術(shù),所以我們有義務(wù)讓自己的代碼能夠直觀體現(xiàn)出盡多的信息,而泛泛的Exception之類,恰恰隱藏了我們的目的。另外。我們也要保證程序不會(huì)捕捉到我們不希望捕獲的異常。比如你可能更希望RuntimeException被擴(kuò)散出來(lái),而不是被捕獲。
進(jìn)一步講,除非深思熟慮了,否者不要捕獲Throwable或者Error,這樣很難保證我們能夠正確程序處理OutOfMemoryError.
第二,不要生吞(swallow)異常。這一異常處理中特別注意的事情,很可能會(huì)導(dǎo)致非常難以診斷的詭異情況。
生吞異常,往往是基于假設(shè)這段代碼可能不會(huì)發(fā)生,或者感覺(jué)忽略異常是無(wú)所謂的,但是千萬(wàn)不要在產(chǎn)品代碼做這種假設(shè)!
如果我們不能把異常拋出來(lái),或者也沒(méi)有輸出到日志(Logger)之類,程序可能會(huì)在后續(xù)代碼以不可控的方式結(jié)束。沒(méi)人能夠輕易判斷究竟是哪里拋出了異常,以及什么原因產(chǎn)生了異常。
再看第二段代碼
try{ //業(yè)務(wù)代碼 }catch(IOException e){ e.printStackTrace(); }
這段代碼作為一段實(shí)驗(yàn)代碼,他沒(méi)有任何問(wèn)題,但是在產(chǎn)品代碼中,通常是不允許這樣處理的。你先考慮一下為什么?
我們先來(lái)看一下printStackTrace()的文檔,開(kāi)頭就是Prints this throwable and its backtrace to the standard error stream問(wèn)題就在這里,在稍微復(fù)雜一點(diǎn)的生產(chǎn)系統(tǒng)中,標(biāo)準(zhǔn)出錯(cuò)(STERR)不是個(gè)合適的輸出選項(xiàng),因?yàn)槟愫茈y判斷出到底輸出到哪里去了。
尤其是分布式系統(tǒng),如果發(fā)生異常,但是無(wú)法找到堆棧軌跡(stacktrace),這純屬是為診斷設(shè)置障礙,所以最好使用產(chǎn)品日志,詳細(xì)地輸出到日志系統(tǒng)里。
我們接下來(lái)看下面的代碼段,體會(huì)一下Throw early,catch late原則。
public void readPreferences(String fileName){ //perform operations... InputStream in = new FileInputStream(fileName); //..read the preferences file... }
如果fileName是null,那么程序就會(huì)拋出NullPointerException,但是由于沒(méi)有第一時(shí)間暴露出問(wèn)題,堆棧信息可能非常令人費(fèi)解,往往需要相對(duì)復(fù)雜的定位。這個(gè)NPE只是作為例子,實(shí)際產(chǎn)品代碼中,可能是各種情況,比如獲取配置失敗之類的。在發(fā)現(xiàn)問(wèn)題的時(shí)候,第一時(shí)間拋出,能夠更加清晰地反映問(wèn)題。
我們可以修改一下,讓問(wèn)題throw early ,對(duì)應(yīng)的異常信息就會(huì)非常直觀了。
public void readPreferences(String fileName){ Objects.requireNonNull(fileName); //perform operations... InputStream in = new FileInputStream(fileName); //..read the preferences file...
至于catch late ,其實(shí)是我們經(jīng)??鄲赖膯?wèn)題,捕獲異常后,需要怎么處理呢?最差的處理方式,就是我們前面提到的“生吞異常”,本質(zhì)上其實(shí)是掩蓋問(wèn)題。如果實(shí)在不知道怎么處理,可以選擇保留原有異常的cause信息,直接再拋出或者構(gòu)建新的異常拋出。在更高層面,因?yàn)橛辛饲逦模I(yè)務(wù))邏輯,往往會(huì)更清楚合適的處理方式是什么。
有的時(shí)候我們會(huì)更具需要自定義異常,這個(gè)時(shí)候除了保證提供足夠的信息,還有兩個(gè)點(diǎn)需要考慮:
- 是否需要定義成Checked Exception,因?yàn)檫@種類型設(shè)計(jì)的初衷就是為了從異常情況下恢復(fù),作為異常設(shè)計(jì)者,我們往往有充足信息進(jìn)行分類。
- 為保證診斷信息足夠的同時(shí),要考慮避免包含敏感信息,因?yàn)槟菢涌赡軐?dǎo)致潛在的安全問(wèn)題。如果我們看java的標(biāo)準(zhǔn)庫(kù),你可能注意到類似java.net.ConnectException,出錯(cuò)信息是類似“connection refused(connection refused)”,而不包含具體的機(jī)器名,ip,端口等,一個(gè)重要考慮就是信息安全。類似的情況在日志中也有,比如,用戶數(shù)據(jù)一般是不可以輸出到日志里面的。
業(yè)界有一種爭(zhēng)論(甚至可以算上是某種程度的共識(shí)),java語(yǔ)言的checked Exception也許是個(gè)設(shè)計(jì)錯(cuò)誤,反對(duì)者列舉幾點(diǎn): - checked Exception的假設(shè)是我們捕獲了異常,然后恢復(fù)程序。但是,其實(shí)我們大多數(shù)情況下,根本不可能恢復(fù)。checked exception的使用,已經(jīng)大大偏離了最初的設(shè)計(jì)目的。
- checked Exception不兼容functional編程,如果你寫過(guò)lambda/stream代碼,相信深有體會(huì)。
很多開(kāi)源項(xiàng)目,已經(jīng)采納了這種實(shí)踐,比如spring,hibernate等,甚至反映在新的編程語(yǔ)言的設(shè)計(jì)中,比如Scala等。
當(dāng)然,很多人覺(jué)得沒(méi)有必要矯枉過(guò)正,因?yàn)榇_實(shí)有一些異常,比如和環(huán)境相關(guān)的IO,網(wǎng)絡(luò)等,其實(shí)是存在可回復(fù)性的,而且java已經(jīng)通過(guò)業(yè)界的海量實(shí)踐,證明了其結(jié)構(gòu)高質(zhì)量軟件的能力。我們就不再進(jìn)一步解讀了。
我們從性能角度來(lái)審視一下java的異常處理機(jī)制,這里有兩個(gè)可能會(huì)相對(duì)昂貴的地方: - try-catch代碼段會(huì)產(chǎn)生額外的性能開(kāi)銷,或者換個(gè)角度說(shuō),他往往會(huì)影響JVM對(duì)代碼進(jìn)行優(yōu)化,所以建議僅捕獲有必要的代碼段,盡量不要一個(gè)大的try包住整段的代碼;與此同時(shí),利用異??刂拼a流程,也不是一個(gè)好主意,遠(yuǎn)比我們通常意義上的條件語(yǔ)句(if/else,switch)要低效
- java每實(shí)例化一個(gè)Exception,都會(huì)對(duì)當(dāng)時(shí)的棧進(jìn)行快照,這是一個(gè)相對(duì)比較重的操作。如果發(fā)生的非常的頻繁,這個(gè)開(kāi)銷可能就不能被忽略了。
所以,對(duì)于部分追求極致性能的底層類庫(kù),有種方式是嘗試創(chuàng)建不進(jìn)行快照的Exception.這本身也存在爭(zhēng)議,因?yàn)檫@樣做的假設(shè)在于,我創(chuàng)建異常時(shí)知道未來(lái)是否需要堆棧。問(wèn)題是實(shí)際上可能嗎?小范圍或許可能,但是在大規(guī)模項(xiàng)目中,這么做可能不是個(gè)理智的選擇。如果需要堆棧,但是沒(méi)有收集這些信息,在復(fù)雜情況下,尤其是類似微服務(wù)這種分布式系統(tǒng),這會(huì)大大增加診斷的難度。
當(dāng)我們的服務(wù)出現(xiàn)反應(yīng)慢,吞吐量下降的時(shí)候,檢查發(fā)生最頻繁的Exception也是一種思路。關(guān)于診斷后臺(tái)變慢的問(wèn)題,我們會(huì)在后面的java性能基礎(chǔ)模塊中系統(tǒng)討論。
今天,我從一個(gè)常見(jiàn)的異常處理概念問(wèn)題,簡(jiǎn)單的總結(jié)了java異常處理機(jī)制,并結(jié)合代碼,分析了一些普遍認(rèn)可的最佳實(shí)踐,以及業(yè)界最新的一些異常使用共識(shí)。最后,我分析了一下異常開(kāi)銷,希望能對(duì)你有所幫助。
一課一練
關(guān)于我們討論的題目你做到心中有數(shù)了嗎?可以思考一個(gè)問(wèn)題,對(duì)于異常處理編程,不同的編程范式也會(huì)影響到異常的處理策略,比如,現(xiàn)在非常火熱的反應(yīng)式編程,因?yàn)槠浔旧硎钱惒?,基于事件機(jī)制的,所以出現(xiàn)異常情況,決不能簡(jiǎn)單的拋出;另外,由于代碼堆棧不再是同步調(diào)用那種垂直結(jié)構(gòu),這里的異常處理和日志需要更加小心,我們看到的往往是特定executor的堆棧,而不是業(yè)務(wù)方法調(diào)用關(guān)系,對(duì)于這種情況,你有什么好的辦法嗎?
到此這篇關(guān)于Java中Exception和Error的區(qū)別詳解的文章就介紹到這了,更多相關(guān)Java Exception和Error區(qū)別內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java異常 Factory method''sqlSessionFactory''rew exception;ested exception is java.lang.NoSuchMethodError:
- SpringBoot異常: nested exception is java.lang.NoClassDefFoundError: javax/servlet/ServletContext解決方案
- Java異常處理操作 Throwable、Exception、Error
- Mybatis配置錯(cuò)誤:java.lang.ExceptionInInitializerError
- Java之Error與Exception的區(qū)別案例詳解
相關(guān)文章
SpringBoot集成P6Spy實(shí)現(xiàn)SQL日志的記錄詳解
P6Spy是一個(gè)框架,它可以無(wú)縫地?cái)r截和記錄數(shù)據(jù)庫(kù)活動(dòng),而無(wú)需更改現(xiàn)有應(yīng)用程序的代碼。一般我們使用的比較多的是使用p6spy打印我們最后執(zhí)行的sql語(yǔ)句2022-11-11IDEA搭建多模塊的Maven項(xiàng)目方式(相互依賴)
這篇文章主要介紹了IDEA搭建多模塊的Maven項(xiàng)目方式(相互依賴),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08springboot文件上傳時(shí)maxPostSize設(shè)置大小失效問(wèn)題及解決
這篇文章主要介紹了springboot文件上傳時(shí)maxPostSize設(shè)置大小失效問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07簡(jiǎn)單談?wù)刯ava的異常處理(Try Catch Finally)
在程序設(shè)計(jì)中,進(jìn)行異常處理是非常關(guān)鍵和重要的一部分。一個(gè)程序的異常處理框架的好壞直接影響到整個(gè)項(xiàng)目的代碼質(zhì)量以及后期維護(hù)成本和難度。2016-03-03Spring Boot LocalDateTime格式化處理的示例詳解
這篇文章主要介紹了Spring Boot LocalDateTime格式化處理的示例詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-10-10Java Socket循環(huán)接收數(shù)據(jù)readLine()阻塞的解決方案
這篇文章主要介紹了Java Socket循環(huán)接收數(shù)據(jù)readLine()阻塞的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08Spring中的AutowireCandidateResolver的具體使用詳解
這篇文章主要介紹了Spring中的AutowireCandidateResolver的具體使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04