日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java常见异常类图(分类了Error/RuntimeExecption、check Exception)

發(fā)布時間:2023/12/10 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java常见异常类图(分类了Error/RuntimeExecption、check Exception) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

?

  • ?Error:表示由JVM所偵測到的無法預(yù)期的錯誤,由于這是屬于JVM層次的嚴(yán)重錯誤,導(dǎo)致JVM無法繼續(xù)執(zhí)行,因此,這是不可捕捉到的,無法采取任何恢復(fù)的操作,頂多只能顯示錯誤信息。
  • Exception:表示可恢復(fù)的例外,這是可捕捉到的。

Java語言規(guī)范對這兩個定義十分簡單,

將派生于Error或者RuntimeException的異常稱為unchecked異常,

所有其他的異常成為checked異常。


Use checked exceptions for recoverable conditions and runtime exceptions for programming errors (Item 58 in 2nd edition)
不過從這句話中我們可以簡單引申一下,也就是說,如果出現(xiàn)了RuntimeException,就一定是程序員自身的問題。比如說,數(shù)組下標(biāo)越界和訪問空指針異常等等,只要你稍加留心這些異常都是在編碼階段可以避免的異常。如果你還是覺得這兩個概念不好區(qū)分,那么“最暴力“的方法就是將常見的RuntimeException背下來,這樣就可以省去很多判斷的時間。

?

Checked Exception(CE)的重要性

有幾個我覺得很重要的,具有突破性的語言特性,Kotlin 并沒有實現(xiàn)。另外我還發(fā)現(xiàn)一個很重要的 Java 特性,被 Kotlin 的設(shè)計者給盲目拋棄了。這就是我今天要講的主題:checked exception。我不知道這個術(shù)語有什么標(biāo)準(zhǔn)的中文翻譯,為了避免引起定義混亂,下文我就把它簡稱為“CE”好了。

先來科普一下 CE 到底是什么吧。Java 要求你必須在函數(shù)的類型里面聲明它可能拋出的異常。比如,你的函數(shù)如果是這樣:

void foo(string filename) throws FileNotFoundException {if (...){throw new FileNotFoundException();}... }

Java 要求你必須在函數(shù)頭部寫上“throws FileNotFoundException”,否則它就不能編譯。這個聲明表示函數(shù)在某些情況下,會拋出 FileNotFoundException 這個異常。由于編譯器看到了這個聲明,它會嚴(yán)格檢查你對 foo 函數(shù)的用法。在調(diào)用 foo 的時候,你必須使用 try-catch 處理這個異常,或者在調(diào)用的函數(shù)頭部也聲明 “throws FileNotFoundException”,把這個異常傳遞給上一層調(diào)用者。

try {foo("blah"); } catch (FileNotFoundException e) {... }

這種對異常的聲明和檢查,叫做“checked exception”。很多語言(包括 C++,C#,JavaScript,Python……)都有異常機(jī)制,但它們不要求你在函數(shù)的類型里面聲明可能出現(xiàn)的異常類型,也不使用靜態(tài)類型系統(tǒng)對異常的處理進(jìn)行檢查和驗證。我們說這些語言里面有“exception”,卻沒有“checked exception”。

理解了 CE 這個概念,下面我們來談?wù)?#xff1a;Kotlin 和 C# 對 CE 的誤解。

Kotlin 的文檔明確的說明,它不支持類似 Java 的 checked exception(CE),指出 CE 的缺點(diǎn)是“繁瑣”,并且列舉了幾個普通程序員心目中“大?!钡奈恼?#xff0c;想以此來證明為什么 Java 的 CE 是一個錯誤,為什么它不解決問題,卻帶來了麻煩。這些人包括了 Bruce Eckel 和 C# 的設(shè)計者 Anders Hejlsberg。

很早的時候我就看過 Hejlsberg 的這些言論。他的話看似有道理,然而通過自己編程和設(shè)計語言的實際經(jīng)驗,我發(fā)現(xiàn)他并沒有抓住問題的關(guān)鍵。他的論述里有好幾處邏輯錯誤,一些自相矛盾,還有一些盲目的臆斷,所以這些言論并沒能說服我。正好相反,實在的項目經(jīng)驗告訴我,CE 是 C# 缺少的一項重要特性,沒有了 CE 會帶來相當(dāng)麻煩的后果。在微軟寫 C# 的時候,我已經(jīng)深刻體會到了缺少 CE 所帶來的困擾。現(xiàn)在我就來講一下,CE 為什么是很重要的語言特性,然后講一下為什么 Hejlsberg 對它的批評是站不住腳的。

首先,寫 C# 代碼時最讓我頭痛的事情之一,就是 C# 沒有 CE。每調(diào)用一個函數(shù)(不管是標(biāo)準(zhǔn)庫函數(shù),第三方庫函數(shù),還是隊友寫的函數(shù),甚至我自己寫的函數(shù)),我都會疑惑這個函數(shù)是否會拋出異常。由于 C# 的函數(shù)類型上不需要標(biāo)記它可能拋出的異常,為了確保一個函數(shù)不會拋出異常,你就需要檢查這個函數(shù)的源代碼,以及它調(diào)用的那些函數(shù)的源代碼……

也就是說,你必須檢查這個函數(shù)的整個“調(diào)用樹”的代碼,才能確信這個函數(shù)不會拋出異常。這樣的調(diào)用樹可以是非常大的。說白了,這就是在用人工對代碼進(jìn)行“全局靜態(tài)分析”,遍歷整個調(diào)用樹。這不但費(fèi)時費(fèi)力,看得你眼花繚亂,還容易漏掉出錯。顯然讓人做這種事情是不現(xiàn)實的,所以絕大部分時候,程序員都不能確信這個函數(shù)調(diào)用不會出現(xiàn)異常。

在這種疑慮的情況下,你就不得不做最壞的打算,你就得把代碼寫成:

try {foo(); } catch (Exception) {... }

注意到了嗎,這也就是你寫 Java 代碼時,能寫出的最糟糕的異常處理代碼!因為不知道 foo 函數(shù)里面會有什么異常出現(xiàn),所以你的 catch 語句里面也不知道該做什么。大部分人只能在里面放一條 log,記錄異常的發(fā)生。這是一種非常糟糕的寫法,不但繁復(fù),而且可能掩蓋運(yùn)行時錯誤。有時候你發(fā)現(xiàn)有些語句莫名其妙沒有執(zhí)行,折騰好久才發(fā)現(xiàn)是因為某個地方拋出了異常,所以跳到了這種 catch 的地方,然后被忽略了。如果你忘了寫 catch (Exception),那么你的代碼可能運(yùn)行了一段時間之后當(dāng)?shù)?#xff0c;因為忽然出現(xiàn)一個測試時沒出現(xiàn)過的異?!?/p>

所以對于 C# 這樣沒有 CE 的語言,很多時候你必須莫名其妙這樣寫,這種做法也就是我在微軟的 C# 代碼里經(jīng)常看到的。問原作者為什么那里要包一層 try-catch,答曰:“因為之前這地方出現(xiàn)了某種異常,所以加了個 try-catch,然后就忘了當(dāng)時出現(xiàn)的是什么異常,具體是哪一條語句會出現(xiàn)異常,總之那一塊代碼會出現(xiàn)異?!?如此寫代碼,自己心虛,看的人也糊涂,軟件質(zhì)量又如何保證?

那么 Java 呢?因為 Java 有 CE,所以當(dāng)你看到一個函數(shù)沒有聲明異常,就可以放心的省掉 try-catch。所以這個 C# 的問題,自然而然就被避免了,你不需要在很多地方疑惑是否需要寫 try-catch。Java 編譯器的靜態(tài)類型檢查會告訴你,在什么地方必須寫 try-catch,或者加上 throws 聲明。如果你用 IntelliJ,把光標(biāo)放到 catch 語句上面,可能拋出那種異常的語句就會被加亮。C# 代碼就不可能得到這樣的幫助。

CE 看起來有點(diǎn)費(fèi)事,似乎只是為了“讓編譯器開心”,然而這其實是每個程序員必須理解的事情。出錯處理并不是 Java 所特有的東西,就算你用 C 語言,也會遇到本質(zhì)一樣的問題。使用任何語言都無法逃脫這個問題,所以必須把它想清楚。在《編程的智慧》一文中,我已經(jīng)講述了如何正確的進(jìn)行出錯處理。如果你濫用 CE,當(dāng)然會有不好的后果,然而如果你使用得當(dāng),就會起到事半功倍,提高代碼可靠性的效果。

Java 的 CE 其實對應(yīng)著一種強(qiáng)大的邏輯概念,一種根本性的語言特性,它叫做“union type”。這個特性只存在于 Typed Racket 等一兩個不怎么流行的語言里。Union type 也存在于 PySonar 類型推導(dǎo)和 Yin 語言里面。你可以把 Java 的 CE 看成是對 union type 的一種不完美的,丑陋的實現(xiàn)。雖然實現(xiàn)丑陋,寫法麻煩,CE 卻仍然有著 union type 的基本功能。如果使用得當(dāng),union type 不但會讓代碼的出錯處理無懈可擊,還可以完美的解決 null 指針等頭痛的問題。通過實際使用 Java 的 CE 和 Typed Racket 的 union type 來構(gòu)建復(fù)雜項目,我很確信 CE 的可行性和它帶來的好處。

現(xiàn)在我來講一下為什么 Hejlsberg 對于 CE 的批評是站不住腳的。他的第一個錯誤,俗話說就是“人笨怪刀鈍”。他把程序員對于出錯處理的無知,不謹(jǐn)慎和誤用,怪罪在 CE 這個無辜的語言特性身上。他的話翻譯過來就是:“因為大部分程序員都很傻,沒有經(jīng)過嚴(yán)格的訓(xùn)練,不小心又懶惰,所以沒法正確使用 CE。所以這個特性不好,是沒用的!”

他的論據(jù)里面充滿了這樣的語言:

  • “大部分程序員不會處理這些 throws 聲明的異常,所以他們就給自己的每個函數(shù)都加上 throws Exception。這使得 Java 的 CE 完全失效?!?/li>
  • “大部分程序員根本不在乎這異常是什么,所以他們在程序的最上層加上 catch (Exception),捕獲所有的異常?!?/li>
  • “有些人的函數(shù)最后拋出 80 多種不同的異常,以至于使用者不知道該怎么辦?!薄?/li>

注意到了嗎,這種給每個函數(shù)加上 throws Exception 或者 catch (Exception) 的做法,也就是我在《編程的智慧》里面指出的經(jīng)典錯誤做法。要讓 CE 可以起到良好的作用,你必須避免這樣的用法,你必須知道自己在干什么,必須知道被調(diào)用的函數(shù)拋出的 exception 是什么含義,必須思考如何正確的處理它們。

另外 CE 就像 union type 一樣,如果你不小心分析,不假思索就拋出異常,就會遇到他提到的“拋出 80 多種異?!钡那闆r。出現(xiàn)這種情況往往是因為程序員沒有仔細(xì)思考,沒有處理本來該自己處理的異常,而只是簡單的把下層的異常加到自己函數(shù)類型里面。在多層調(diào)用之后,你就會發(fā)現(xiàn)最上面的函數(shù)累積起很多種異常,讓調(diào)用者不知所措,只好傳遞這些異常,造成惡性循環(huán)。終于有人煩得不行,把它改成了“throws Exception”。

我在使用 Typed Racket 的 union type 時也遇到了類似的問題,但只要你嚴(yán)格檢查被調(diào)用函數(shù)的異常,盡量不讓它們傳播,嚴(yán)格限制自己拋出的異常數(shù)目,縮小可能出現(xiàn)的異常范圍,這種情況是可以避免的。CE 和 union type 強(qiáng)迫你仔細(xì)的思考,理順這些東西之后,你就會發(fā)現(xiàn)代碼變得非常縝密而優(yōu)雅。其實就算你寫 C 代碼或者 JavaScript,這些問題是同樣存在的,只不過這些語言沒有強(qiáng)迫你去思考,所以很多時候問題被稀里糊涂掩蓋了起來,直到很長時間之后才暴露出來,不可救藥。

所以可以說,這些問題來自于程序員自己,而不是 CE 本身。CE 只提供了一種機(jī)制,至于程序員怎么使用它,是他們自己的職責(zé)。再好的特性被濫用,也會產(chǎn)生糟糕的結(jié)果。Hejlsberg 對這些問題使用了站不住腳的理論。如果你假設(shè)程序員都是糊里糊涂寫代碼,那么你可以得出無比驚人的結(jié)論:所有用于防止錯誤的語言特性都是沒用的!因為總有人可以懶到不理解這些特性的用法,所以他總是可以濫用它們,繞過它們,寫出錯誤百出的代碼,所以靜態(tài)類型沒用,CE 沒用,…… 有這些特性的語言都是垃圾,大家都寫 PHP 就行了 ;)

Hejlsberg 把這些不理解 CE 用法,懶惰,濫用它的人作為依據(jù),以至于得出 CE 是沒用的特性,以至于不把它放到 C# 里面。由于某些人會誤用 CE,結(jié)果就讓真正理解它的人也不能用它。最后所有人都退化到最笨的情況,大家都只好寫 catch (Exception)。在 Java 里,至少有少數(shù)人知道應(yīng)該怎么做,在 C# 里,所有人都被迫退化成最差的 Java 程序員 ;)

另外,Hejlsberg 還指出 C# 代碼里沒有被 catch 的異常,應(yīng)該可以用“靜態(tài)分析”檢查出來。可以看出來,他并不理解這種靜態(tài)檢查是什么規(guī)模的問題。要能用靜態(tài)分析發(fā)現(xiàn) C# 代碼里被忽略的異常,你必須進(jìn)行“全局分析”,也就是說為了知道一個函數(shù)是否會拋出異常,你不能只看這個函數(shù)。你必須分析這個函數(shù)的代碼,它調(diào)用的代碼,它調(diào)用的代碼調(diào)用的代碼…… 所以你需要分析超乎想象的代碼量,而且很多時候你沒有源代碼。所以對于大型的項目,這顯然是不現(xiàn)實的。

相比之下,Java 要求你對異常進(jìn)行 throws 顯式聲明,實質(zhì)上把這個全局分析問題分解成了一個個模塊化(modular)的小問題。每個函數(shù)作者完成其中的一部分,調(diào)用它的人完成另外一部分。大家合力幫助編譯器,高效的完成靜態(tài)檢查,防止漏掉異常處理,避免不必要的 try-catch。實際上,像 Exceptional 一類的 C# 靜態(tài)檢查工具,會要求你在注釋里寫出可能拋出的異常,這樣它才能發(fā)現(xiàn)被忽略的異常。所以 Exceptional 其實重新發(fā)明了 Java 的 CE,只不過 throws 聲明被寫成了一個注釋而已。

說到 C#,其實它還有另外一個特別討厭的設(shè)計錯誤,引起了很多不必要的麻煩。感興趣的人可以看看我這篇文章:《可惡的 C# IDisposable 接口》。這個問題浪費(fèi)了整個團(tuán)隊兩個月之久的時間。所以我覺得作為 C# 的設(shè)計者,Hejlsberg 的思維局限性相當(dāng)大。我們應(yīng)該小心的分析和論證這些人的言論,不應(yīng)該把他們作為權(quán)威而盲目接受,以至于讓一個優(yōu)秀的語言特性被誤解,不能進(jìn)入到新的語言里。

結(jié)論?

所以我對 Kotlin 是什么“結(jié)論”呢?我沒有結(jié)論,這篇文章就像我所有的看法一樣,僅供參考。顯然 Kotlin 有的地方做得比 Java 好,所以它不會因為沒有 CE 而完全失去意義。我不想打擊人們對新事物的興趣,我甚至鼓勵有時間的人去試試看。

我知道很多人希望我給他們一個結(jié)論,到底是用一個語言,還是不用它,這樣他們就不用糾結(jié)了,然而我并不想給出一個結(jié)論。一來是因為我不想讓人感覺我在“控制”他們,如何看待一個東西是他們的自由,是否采用一個東西是他們自己的決定。二來是因為我還沒有時間和機(jī)會,去用 Kotlin 來做實際的項目。另外,我早就厭倦了試用新的語言,如果一個大眾化的語言沒有特別討厭,不可原諒的設(shè)計失誤,我是不會輕易換用新語言的。我寧愿讓其他人做我的小白鼠,去試用這些新語言。到后來我有空了,再去看看他們的成功或者失敗經(jīng)歷 :P

所以對我個人而言,我至少現(xiàn)在不會去用 Kotlin,但我并不想讓其他人也跟我一樣。因為 Java,C++ 和 C 已經(jīng)能滿足我的需求,它們相當(dāng)穩(wěn)定,而且我對它們已經(jīng)很熟悉,所以我為什么要花精力去學(xué)一個新的語言,去折騰不成熟的工具,放下我真正感興趣的算法和數(shù)據(jù)結(jié)構(gòu)等問題呢?實際上不管我用什么語言寫代碼,我的頭腦里都在用同一個語言構(gòu)造程序。我寫代碼的過程,只不過是在為我腦子里的“萬能語言”找到對應(yīng)的表達(dá)方式而已。

總結(jié)

以上是生活随笔為你收集整理的java常见异常类图(分类了Error/RuntimeExecption、check Exception)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。