java常见异常类图(分类了Error/RuntimeExecption、check Exception)
?
- ?Error:表示由JVM所偵測(cè)到的無(wú)法預(yù)期的錯(cuò)誤,由于這是屬于JVM層次的嚴(yán)重錯(cuò)誤,導(dǎo)致JVM無(wú)法繼續(xù)執(zhí)行,因此,這是不可捕捉到的,無(wú)法采取任何恢復(fù)的操作,頂多只能顯示錯(cuò)誤信息。
- Exception:表示可恢復(fù)的例外,這是可捕捉到的。
Java語(yǔ)言規(guī)范對(duì)這兩個(gè)定義十分簡(jiǎn)單,
將派生于Error或者RuntimeException的異常稱為unchecked異常,
所有其他的異常成為checked異常。
Use checked exceptions for recoverable conditions and runtime exceptions for programming errors (Item 58 in 2nd edition)
不過(guò)從這句話中我們可以簡(jiǎn)單引申一下,也就是說(shuō),如果出現(xiàn)了RuntimeException,就一定是程序員自身的問(wèn)題。比如說(shuō),數(shù)組下標(biāo)越界和訪問(wèn)空指針異常等等,只要你稍加留心這些異常都是在編碼階段可以避免的異常。如果你還是覺(jué)得這兩個(gè)概念不好區(qū)分,那么“最暴力“的方法就是將常見(jiàn)的RuntimeException背下來(lái),這樣就可以省去很多判斷的時(shí)間。
?
Checked Exception(CE)的重要性
有幾個(gè)我覺(jué)得很重要的,具有突破性的語(yǔ)言特性,Kotlin 并沒(méi)有實(shí)現(xiàn)。另外我還發(fā)現(xiàn)一個(gè)很重要的 Java 特性,被 Kotlin 的設(shè)計(jì)者給盲目拋棄了。這就是我今天要講的主題:checked exception。我不知道這個(gè)術(shù)語(yǔ)有什么標(biāo)準(zhǔn)的中文翻譯,為了避免引起定義混亂,下文我就把它簡(jiǎn)稱為“CE”好了。
先來(lái)科普一下 CE 到底是什么吧。Java 要求你必須在函數(shù)的類型里面聲明它可能拋出的異常。比如,你的函數(shù)如果是這樣:
void foo(string filename) throws FileNotFoundException {if (...){throw new FileNotFoundException();}... }Java 要求你必須在函數(shù)頭部寫(xiě)上“throws FileNotFoundException”,否則它就不能編譯。這個(gè)聲明表示函數(shù)在某些情況下,會(huì)拋出 FileNotFoundException 這個(gè)異常。由于編譯器看到了這個(gè)聲明,它會(huì)嚴(yán)格檢查你對(duì) foo 函數(shù)的用法。在調(diào)用 foo 的時(shí)候,你必須使用 try-catch 處理這個(gè)異常,或者在調(diào)用的函數(shù)頭部也聲明 “throws FileNotFoundException”,把這個(gè)異常傳遞給上一層調(diào)用者。
try {foo("blah"); } catch (FileNotFoundException e) {... }這種對(duì)異常的聲明和檢查,叫做“checked exception”。很多語(yǔ)言(包括 C++,C#,JavaScript,Python……)都有異常機(jī)制,但它們不要求你在函數(shù)的類型里面聲明可能出現(xiàn)的異常類型,也不使用靜態(tài)類型系統(tǒng)對(duì)異常的處理進(jìn)行檢查和驗(yàn)證。我們說(shuō)這些語(yǔ)言里面有“exception”,卻沒(méi)有“checked exception”。
理解了 CE 這個(gè)概念,下面我們來(lái)談?wù)?#xff1a;Kotlin 和 C# 對(duì) CE 的誤解。
Kotlin 的文檔明確的說(shuō)明,它不支持類似 Java 的 checked exception(CE),指出 CE 的缺點(diǎn)是“繁瑣”,并且列舉了幾個(gè)普通程序員心目中“大牛”的文章,想以此來(lái)證明為什么 Java 的 CE 是一個(gè)錯(cuò)誤,為什么它不解決問(wèn)題,卻帶來(lái)了麻煩。這些人包括了 Bruce Eckel 和 C# 的設(shè)計(jì)者 Anders Hejlsberg。
很早的時(shí)候我就看過(guò) Hejlsberg 的這些言論。他的話看似有道理,然而通過(guò)自己編程和設(shè)計(jì)語(yǔ)言的實(shí)際經(jīng)驗(yàn),我發(fā)現(xiàn)他并沒(méi)有抓住問(wèn)題的關(guān)鍵。他的論述里有好幾處邏輯錯(cuò)誤,一些自相矛盾,還有一些盲目的臆斷,所以這些言論并沒(méi)能說(shuō)服我。正好相反,實(shí)在的項(xiàng)目經(jīng)驗(yàn)告訴我,CE 是 C# 缺少的一項(xiàng)重要特性,沒(méi)有了 CE 會(huì)帶來(lái)相當(dāng)麻煩的后果。在微軟寫(xiě) C# 的時(shí)候,我已經(jīng)深刻體會(huì)到了缺少 CE 所帶來(lái)的困擾。現(xiàn)在我就來(lái)講一下,CE 為什么是很重要的語(yǔ)言特性,然后講一下為什么 Hejlsberg 對(duì)它的批評(píng)是站不住腳的。
首先,寫(xiě) C# 代碼時(shí)最讓我頭痛的事情之一,就是 C# 沒(méi)有 CE。每調(diào)用一個(gè)函數(shù)(不管是標(biāo)準(zhǔn)庫(kù)函數(shù),第三方庫(kù)函數(shù),還是隊(duì)友寫(xiě)的函數(shù),甚至我自己寫(xiě)的函數(shù)),我都會(huì)疑惑這個(gè)函數(shù)是否會(huì)拋出異常。由于 C# 的函數(shù)類型上不需要標(biāo)記它可能拋出的異常,為了確保一個(gè)函數(shù)不會(huì)拋出異常,你就需要檢查這個(gè)函數(shù)的源代碼,以及它調(diào)用的那些函數(shù)的源代碼……
也就是說(shuō),你必須檢查這個(gè)函數(shù)的整個(gè)“調(diào)用樹(shù)”的代碼,才能確信這個(gè)函數(shù)不會(huì)拋出異常。這樣的調(diào)用樹(shù)可以是非常大的。說(shuō)白了,這就是在用人工對(duì)代碼進(jìn)行“全局靜態(tài)分析”,遍歷整個(gè)調(diào)用樹(shù)。這不但費(fèi)時(shí)費(fèi)力,看得你眼花繚亂,還容易漏掉出錯(cuò)。顯然讓人做這種事情是不現(xiàn)實(shí)的,所以絕大部分時(shí)候,程序員都不能確信這個(gè)函數(shù)調(diào)用不會(huì)出現(xiàn)異常。
在這種疑慮的情況下,你就不得不做最壞的打算,你就得把代碼寫(xiě)成:
try {foo(); } catch (Exception) {... }注意到了嗎,這也就是你寫(xiě) Java 代碼時(shí),能寫(xiě)出的最糟糕的異常處理代碼!因?yàn)椴恢?foo 函數(shù)里面會(huì)有什么異常出現(xiàn),所以你的 catch 語(yǔ)句里面也不知道該做什么。大部分人只能在里面放一條 log,記錄異常的發(fā)生。這是一種非常糟糕的寫(xiě)法,不但繁復(fù),而且可能掩蓋運(yùn)行時(shí)錯(cuò)誤。有時(shí)候你發(fā)現(xiàn)有些語(yǔ)句莫名其妙沒(méi)有執(zhí)行,折騰好久才發(fā)現(xiàn)是因?yàn)槟硞€(gè)地方拋出了異常,所以跳到了這種 catch 的地方,然后被忽略了。如果你忘了寫(xiě) catch (Exception),那么你的代碼可能運(yùn)行了一段時(shí)間之后當(dāng)?shù)?#xff0c;因?yàn)楹鋈怀霈F(xiàn)一個(gè)測(cè)試時(shí)沒(méi)出現(xiàn)過(guò)的異常……
所以對(duì)于 C# 這樣沒(méi)有 CE 的語(yǔ)言,很多時(shí)候你必須莫名其妙這樣寫(xiě),這種做法也就是我在微軟的 C# 代碼里經(jīng)常看到的。問(wèn)原作者為什么那里要包一層 try-catch,答曰:“因?yàn)橹斑@地方出現(xiàn)了某種異常,所以加了個(gè) try-catch,然后就忘了當(dāng)時(shí)出現(xiàn)的是什么異常,具體是哪一條語(yǔ)句會(huì)出現(xiàn)異常,總之那一塊代碼會(huì)出現(xiàn)異常……” 如此寫(xiě)代碼,自己心虛,看的人也糊涂,軟件質(zhì)量又如何保證?
那么 Java 呢?因?yàn)?Java 有 CE,所以當(dāng)你看到一個(gè)函數(shù)沒(méi)有聲明異常,就可以放心的省掉 try-catch。所以這個(gè) C# 的問(wèn)題,自然而然就被避免了,你不需要在很多地方疑惑是否需要寫(xiě) try-catch。Java 編譯器的靜態(tài)類型檢查會(huì)告訴你,在什么地方必須寫(xiě) try-catch,或者加上 throws 聲明。如果你用 IntelliJ,把光標(biāo)放到 catch 語(yǔ)句上面,可能拋出那種異常的語(yǔ)句就會(huì)被加亮。C# 代碼就不可能得到這樣的幫助。
CE 看起來(lái)有點(diǎn)費(fèi)事,似乎只是為了“讓編譯器開(kāi)心”,然而這其實(shí)是每個(gè)程序員必須理解的事情。出錯(cuò)處理并不是 Java 所特有的東西,就算你用 C 語(yǔ)言,也會(huì)遇到本質(zhì)一樣的問(wèn)題。使用任何語(yǔ)言都無(wú)法逃脫這個(gè)問(wèn)題,所以必須把它想清楚。在《編程的智慧》一文中,我已經(jīng)講述了如何正確的進(jìn)行出錯(cuò)處理。如果你濫用 CE,當(dāng)然會(huì)有不好的后果,然而如果你使用得當(dāng),就會(huì)起到事半功倍,提高代碼可靠性的效果。
Java 的 CE 其實(shí)對(duì)應(yīng)著一種強(qiáng)大的邏輯概念,一種根本性的語(yǔ)言特性,它叫做“union type”。這個(gè)特性只存在于 Typed Racket 等一兩個(gè)不怎么流行的語(yǔ)言里。Union type 也存在于 PySonar 類型推導(dǎo)和 Yin 語(yǔ)言里面。你可以把 Java 的 CE 看成是對(duì) union type 的一種不完美的,丑陋的實(shí)現(xiàn)。雖然實(shí)現(xiàn)丑陋,寫(xiě)法麻煩,CE 卻仍然有著 union type 的基本功能。如果使用得當(dāng),union type 不但會(huì)讓代碼的出錯(cuò)處理無(wú)懈可擊,還可以完美的解決 null 指針等頭痛的問(wèn)題。通過(guò)實(shí)際使用 Java 的 CE 和 Typed Racket 的 union type 來(lái)構(gòu)建復(fù)雜項(xiàng)目,我很確信 CE 的可行性和它帶來(lái)的好處。
現(xiàn)在我來(lái)講一下為什么 Hejlsberg 對(duì)于 CE 的批評(píng)是站不住腳的。他的第一個(gè)錯(cuò)誤,俗話說(shuō)就是“人笨怪刀鈍”。他把程序員對(duì)于出錯(cuò)處理的無(wú)知,不謹(jǐn)慎和誤用,怪罪在 CE 這個(gè)無(wú)辜的語(yǔ)言特性身上。他的話翻譯過(guò)來(lái)就是:“因?yàn)榇蟛糠殖绦騿T都很傻,沒(méi)有經(jīng)過(guò)嚴(yán)格的訓(xùn)練,不小心又懶惰,所以沒(méi)法正確使用 CE。所以這個(gè)特性不好,是沒(méi)用的!”
他的論據(jù)里面充滿了這樣的語(yǔ)言:
- “大部分程序員不會(huì)處理這些 throws 聲明的異常,所以他們就給自己的每個(gè)函數(shù)都加上 throws Exception。這使得 Java 的 CE 完全失效。”
- “大部分程序員根本不在乎這異常是什么,所以他們?cè)诔绦虻淖钌蠈蛹由?catch (Exception),捕獲所有的異常。”
- “有些人的函數(shù)最后拋出 80 多種不同的異常,以至于使用者不知道該怎么辦。”……
注意到了嗎,這種給每個(gè)函數(shù)加上 throws Exception 或者 catch (Exception) 的做法,也就是我在《編程的智慧》里面指出的經(jīng)典錯(cuò)誤做法。要讓 CE 可以起到良好的作用,你必須避免這樣的用法,你必須知道自己在干什么,必須知道被調(diào)用的函數(shù)拋出的 exception 是什么含義,必須思考如何正確的處理它們。
另外 CE 就像 union type 一樣,如果你不小心分析,不假思索就拋出異常,就會(huì)遇到他提到的“拋出 80 多種異常”的情況。出現(xiàn)這種情況往往是因?yàn)槌绦騿T沒(méi)有仔細(xì)思考,沒(méi)有處理本來(lái)該自己處理的異常,而只是簡(jiǎn)單的把下層的異常加到自己函數(shù)類型里面。在多層調(diào)用之后,你就會(huì)發(fā)現(xiàn)最上面的函數(shù)累積起很多種異常,讓調(diào)用者不知所措,只好傳遞這些異常,造成惡性循環(huán)。終于有人煩得不行,把它改成了“throws Exception”。
我在使用 Typed Racket 的 union type 時(shí)也遇到了類似的問(wèn)題,但只要你嚴(yán)格檢查被調(diào)用函數(shù)的異常,盡量不讓它們傳播,嚴(yán)格限制自己拋出的異常數(shù)目,縮小可能出現(xiàn)的異常范圍,這種情況是可以避免的。CE 和 union type 強(qiáng)迫你仔細(xì)的思考,理順這些東西之后,你就會(huì)發(fā)現(xiàn)代碼變得非常縝密而優(yōu)雅。其實(shí)就算你寫(xiě) C 代碼或者 JavaScript,這些問(wèn)題是同樣存在的,只不過(guò)這些語(yǔ)言沒(méi)有強(qiáng)迫你去思考,所以很多時(shí)候問(wèn)題被稀里糊涂掩蓋了起來(lái),直到很長(zhǎng)時(shí)間之后才暴露出來(lái),不可救藥。
所以可以說(shuō),這些問(wèn)題來(lái)自于程序員自己,而不是 CE 本身。CE 只提供了一種機(jī)制,至于程序員怎么使用它,是他們自己的職責(zé)。再好的特性被濫用,也會(huì)產(chǎn)生糟糕的結(jié)果。Hejlsberg 對(duì)這些問(wèn)題使用了站不住腳的理論。如果你假設(shè)程序員都是糊里糊涂寫(xiě)代碼,那么你可以得出無(wú)比驚人的結(jié)論:所有用于防止錯(cuò)誤的語(yǔ)言特性都是沒(méi)用的!因?yàn)榭傆腥丝梢詰械讲焕斫膺@些特性的用法,所以他總是可以濫用它們,繞過(guò)它們,寫(xiě)出錯(cuò)誤百出的代碼,所以靜態(tài)類型沒(méi)用,CE 沒(méi)用,…… 有這些特性的語(yǔ)言都是垃圾,大家都寫(xiě) PHP 就行了 ;)
Hejlsberg 把這些不理解 CE 用法,懶惰,濫用它的人作為依據(jù),以至于得出 CE 是沒(méi)用的特性,以至于不把它放到 C# 里面。由于某些人會(huì)誤用 CE,結(jié)果就讓真正理解它的人也不能用它。最后所有人都退化到最笨的情況,大家都只好寫(xiě) catch (Exception)。在 Java 里,至少有少數(shù)人知道應(yīng)該怎么做,在 C# 里,所有人都被迫退化成最差的 Java 程序員 ;)
另外,Hejlsberg 還指出 C# 代碼里沒(méi)有被 catch 的異常,應(yīng)該可以用“靜態(tài)分析”檢查出來(lái)。可以看出來(lái),他并不理解這種靜態(tài)檢查是什么規(guī)模的問(wèn)題。要能用靜態(tài)分析發(fā)現(xiàn) C# 代碼里被忽略的異常,你必須進(jìn)行“全局分析”,也就是說(shuō)為了知道一個(gè)函數(shù)是否會(huì)拋出異常,你不能只看這個(gè)函數(shù)。你必須分析這個(gè)函數(shù)的代碼,它調(diào)用的代碼,它調(diào)用的代碼調(diào)用的代碼…… 所以你需要分析超乎想象的代碼量,而且很多時(shí)候你沒(méi)有源代碼。所以對(duì)于大型的項(xiàng)目,這顯然是不現(xiàn)實(shí)的。
相比之下,Java 要求你對(duì)異常進(jìn)行 throws 顯式聲明,實(shí)質(zhì)上把這個(gè)全局分析問(wèn)題分解成了一個(gè)個(gè)模塊化(modular)的小問(wèn)題。每個(gè)函數(shù)作者完成其中的一部分,調(diào)用它的人完成另外一部分。大家合力幫助編譯器,高效的完成靜態(tài)檢查,防止漏掉異常處理,避免不必要的 try-catch。實(shí)際上,像 Exceptional 一類的 C# 靜態(tài)檢查工具,會(huì)要求你在注釋里寫(xiě)出可能拋出的異常,這樣它才能發(fā)現(xiàn)被忽略的異常。所以 Exceptional 其實(shí)重新發(fā)明了 Java 的 CE,只不過(guò) throws 聲明被寫(xiě)成了一個(gè)注釋而已。
說(shuō)到 C#,其實(shí)它還有另外一個(gè)特別討厭的設(shè)計(jì)錯(cuò)誤,引起了很多不必要的麻煩。感興趣的人可以看看我這篇文章:《可惡的 C# IDisposable 接口》。這個(gè)問(wèn)題浪費(fèi)了整個(gè)團(tuán)隊(duì)兩個(gè)月之久的時(shí)間。所以我覺(jué)得作為 C# 的設(shè)計(jì)者,Hejlsberg 的思維局限性相當(dāng)大。我們應(yīng)該小心的分析和論證這些人的言論,不應(yīng)該把他們作為權(quán)威而盲目接受,以至于讓一個(gè)優(yōu)秀的語(yǔ)言特性被誤解,不能進(jìn)入到新的語(yǔ)言里。
結(jié)論?
所以我對(duì) Kotlin 是什么“結(jié)論”呢?我沒(méi)有結(jié)論,這篇文章就像我所有的看法一樣,僅供參考。顯然 Kotlin 有的地方做得比 Java 好,所以它不會(huì)因?yàn)闆](méi)有 CE 而完全失去意義。我不想打擊人們對(duì)新事物的興趣,我甚至鼓勵(lì)有時(shí)間的人去試試看。
我知道很多人希望我給他們一個(gè)結(jié)論,到底是用一個(gè)語(yǔ)言,還是不用它,這樣他們就不用糾結(jié)了,然而我并不想給出一個(gè)結(jié)論。一來(lái)是因?yàn)槲也幌胱屓烁杏X(jué)我在“控制”他們,如何看待一個(gè)東西是他們的自由,是否采用一個(gè)東西是他們自己的決定。二來(lái)是因?yàn)槲疫€沒(méi)有時(shí)間和機(jī)會(huì),去用 Kotlin 來(lái)做實(shí)際的項(xiàng)目。另外,我早就厭倦了試用新的語(yǔ)言,如果一個(gè)大眾化的語(yǔ)言沒(méi)有特別討厭,不可原諒的設(shè)計(jì)失誤,我是不會(huì)輕易換用新語(yǔ)言的。我寧愿讓其他人做我的小白鼠,去試用這些新語(yǔ)言。到后來(lái)我有空了,再去看看他們的成功或者失敗經(jīng)歷 :P
所以對(duì)我個(gè)人而言,我至少現(xiàn)在不會(huì)去用 Kotlin,但我并不想讓其他人也跟我一樣。因?yàn)?Java,C++ 和 C 已經(jīng)能滿足我的需求,它們相當(dāng)穩(wěn)定,而且我對(duì)它們已經(jīng)很熟悉,所以我為什么要花精力去學(xué)一個(gè)新的語(yǔ)言,去折騰不成熟的工具,放下我真正感興趣的算法和數(shù)據(jù)結(jié)構(gòu)等問(wèn)題呢?實(shí)際上不管我用什么語(yǔ)言寫(xiě)代碼,我的頭腦里都在用同一個(gè)語(yǔ)言構(gòu)造程序。我寫(xiě)代碼的過(guò)程,只不過(guò)是在為我腦子里的“萬(wàn)能語(yǔ)言”找到對(duì)應(yīng)的表達(dá)方式而已。
總結(jié)
以上是生活随笔為你收集整理的java常见异常类图(分类了Error/RuntimeExecption、check Exception)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 兴业桃花信用卡怎么申请/申请条件
- 下一篇: ROS技术点滴 —— MoveIt!中的