[轉(zhuǎn)]Java 和 .Net 在異常處理機(jī)制上的區(qū)別
java.netexception編譯器虛擬機(jī)application
關(guān)于 Java 和 .Net 優(yōu)劣的爭論一直在繼續(xù),而在異常處理方面體現(xiàn)得最為激烈,因?yàn)樗麄冎g的差異是如此明顯。.Net 晚于 Java 出現(xiàn),那么 Java 對 .Net 就理應(yīng)起到很重要的借鑒作用,但是偉大的 Anders Hejlsberg 為什么沒有繼續(xù) Java 的實(shí)現(xiàn)方式,而是另辟蹊徑,這是一個非常值得研究的問題。因?yàn)槲覀円姓J(rèn)一個真理:正確的東西大家都是一樣的正確,錯誤的卻各有個的錯誤??梢钥隙ㄟ@不可 能是 Anders 的疏忽,那么他的道理究竟何在,或者說他們之間究竟有什么區(qū)別?
在你能耐下心來看完這篇帖子之前,我想要明確告訴你一個結(jié)論:Java 和 .Net 在異常處理的本質(zhì)上是沒有區(qū)別的。
一、Java 是如何處理異常的
如果一個 Java 方法要拋出異常,那么需要在這個方法后面用 throws 關(guān)鍵字定義可以拋出的異常類型。倘若沒有定義,就認(rèn)為該方法不拋出任何異常。如果從方法的入口和出口的角度去考慮一下這個規(guī)范,我們知道參數(shù)可以認(rèn)為是方 法的入口(當(dāng)然某些情況下也可以是出口),而返回值則是方法的出口,這是在程序正常執(zhí)行的情況下,數(shù)據(jù)從入口入,出口出。要是程序非正常執(zhí)行,產(chǎn)生異常又 當(dāng)如何? 被拋出的異常應(yīng)該如何從方法中釋放出來呢? Java 的這種語法規(guī)范就如同給異常開了一個后門,讓異??梢蕴枚手?#8220;正確”地從方法里被拋出。
這樣的規(guī)范決定了 Java 語法必須強(qiáng)行對異常進(jìn)行 try-catch。設(shè)想一下,對于以下的方法簽名:
public void foo() throws BarException { ... }
暗含了兩方面的意思:第一,該方法要拋出 BarException 類型的異常;第二,除了 BarException 外不能拋出其他的異常。而正是這第二點(diǎn)的緣故,我們要如何保證沒有除 BarException 之外的任何異常被拋出呢? 很顯然,就需要 try-catch 其他的異常。也就是說,一般情況下,方法不拋出哪些異常就要在方法內(nèi)部 try-catch 這些異常。
Java 這樣的機(jī)制既有優(yōu)點(diǎn),也有缺點(diǎn)。先來說說優(yōu)點(diǎn):
很顯然,這種規(guī)范是由 Java 編譯器決定的。倘若 Java 程序的入口點(diǎn) main() 方法沒有任何異常拋出,就是說要在 main() 方法內(nèi)部,即整個程序內(nèi)部捕捉所有的異常,否則將無法通過編譯。這樣編譯器保證了程序?qū)γ總€異常都有相應(yīng)的計(jì)劃和處理,不會有未處理的異常被泄露到虛擬機(jī) 中,導(dǎo)致程序意外中斷或退出,也就是增強(qiáng)了程序的健壯性。當(dāng)然,Java 有 RuntimeException 的概念,這樣的異常仍然可以隨時被拋出到虛擬機(jī)中。
強(qiáng)行 try-catch 要求把異常作為程序設(shè)計(jì)的一部分看待。就如同方法的參數(shù)和返回值一樣,在編寫一個方法時,要結(jié)合上下文做出通盤的考慮和打算。雖然異常是所謂的“意外情況”,但是這些“例外”理應(yīng)是被我們?nèi)苛私獠⑻幚淼摹?/div>
方便調(diào)試。異常理應(yīng)在正確的位置被捕捉。當(dāng)異常發(fā)生時,我們能更清楚的了解到其來源和相應(yīng)處理程序的位置,而免去了在整個調(diào)用棧中摸索的麻煩。
在不借助任何文檔的情況下,從方法簽名就可以知曉應(yīng)該對哪些異常進(jìn)行處理。
Java 異常處理機(jī)制的這些優(yōu)點(diǎn)也直接導(dǎo)致了他的致命弱點(diǎn):將程序變得異常繁復(fù)。往往一個簡單的程序,功能代碼寥寥幾行,而異常處理部分卻占用了程序的絕大部分篇 幅;同時導(dǎo)致縮進(jìn)深度加深,既不利于書寫,也不利于閱讀。另外他的強(qiáng)行 try-catch 需要程序員有更高深的造詣,能夠通盤考慮異常處理設(shè)計(jì)問題,這個在程序開始之初或者對于初學(xué)者是一個不小的門檻。這往往會阻礙其推廣與發(fā)展,因?yàn)榈退匠?學(xué)者的信心往往因此而受到打擊。然而對于高手來說,編譯器是否能幫助他們找到未被處理的異常只是一個方便與否的問題,只要在編寫方法時注意了異常處理,即 便沒有編譯器的支持,情況也不會糟糕太多。反而倒是由于要遵循這樣復(fù)雜的異常處理規(guī)范,以至于大多數(shù)人都可能為了圖一時方便,對異常的基類型 Exception 或 Throwable 進(jìn)行籠統(tǒng)地捕捉,這樣做的危害就是那些你無法處理的異常被溺死在處理程序中,(按照異常處理原則,我們應(yīng)該只捕捉那些可以被處理或恢復(fù)的異常,而把其他的 異常繼續(xù)拋出。至于這樣做的優(yōu)勢,以及不這樣做所帶來的問題,不是一兩句能夠說清楚,這里就不展開討論了。)導(dǎo)致程序的不穩(wěn)定和不確定。既沒有發(fā)揮 Java 語法在這方面的優(yōu)勢,反而增加了憂患。
二、.Net 是如何處理異常的
一句話概括 .Net 的異常處理方式就是隨心所欲。沒有人要求你一定要拋出異常,也更沒有人要求你一定要捕捉異常。未被捕捉的異常會被以 Unhandled Exception 的形式拋出到虛擬機(jī)中。在此我就要先解決一下文章開頭提到的問題,為什么說 Java 和 .Net 這兩種異常處理機(jī)制在本質(zhì)上是相同的。可以從兩個方面來考慮:
默認(rèn)情況下。Java 在默認(rèn)情況下 main() 方法是不拋出異常的,正如前面所說的,這要求所有的異常都必須在 main() 方法內(nèi)部被捕捉;而 .Net 則沒有這種約束,他的 Main() 以至于整個應(yīng)用程序中的任何一個方法對異常都是完全開放的。這樣來看,這兩者剛好是對立互補(bǔ)的。
非默認(rèn)情況下。Java 可以通過在 main() 方法后面加 throws 關(guān)鍵字使得整個應(yīng)用程序?qū)Ξ惓i_放;而 .Net 則可以通過給應(yīng)用程序域(Application Domain)的 UnhandledException 事件添加委托達(dá)到捕捉所有異常的目的,很顯然這又是對立互補(bǔ)的。
因此,就好像一個是“正反”,一個是“反正”,加在一起“正反反正”都是一樣的,對于達(dá)到控制異常的目錄來說,是沒有區(qū)別的。
很多 Java 愛好者都鄙視 .Net 的這種行為,一方面他令程序變得不夠健壯,因?yàn)槟J(rèn)情況下沒有強(qiáng)制的辦法要求所有的異常都被處理,或被正確處理;另外,他為調(diào)試增加了困難,不借助文檔或 代碼你將無法了解到一個方法可能拋出什么異常,而當(dāng)一個異常被拋出的時候,同時異常處理代碼又寫得不夠完善,你將不得不仔細(xì)查看整個調(diào)用棧來確定異常出現(xiàn) 的位置,而對于這一點(diǎn) Java 默認(rèn)是強(qiáng)制的。
但是 Anders 的想法總是有道理的。
.Net 代碼寫起來非常容易。這是對于初學(xué)者,或者那些只是想實(shí)現(xiàn)一些測試性小功能的人而言,你完全沒有必要考慮太多異常處理的細(xì)節(jié),你要的就是寫代碼,然后讓他 跑起來。這樣的簡單性無疑是你希望看到的,這樣的簡單性無疑更有利于 .Net 在市場上的推廣。由于他在這方面并沒有什么理論上的漏洞,也就仍然適合構(gòu)建龐大的項(xiàng)目,只是感覺沒有那么舒服罷了。
一定程度上增加了程序的安全性。難道不捕捉異??梢员怀蔀槭前踩膯?這個話也許要從另外一方面來想,前面說過,有些 Java 程序員(絕對不占少數(shù))為了圖省事,在強(qiáng)行捕捉異常的壓迫下,選擇捕捉異常的基類型,也就是捕捉所有的異常。這樣當(dāng)有你無法處理的異常出現(xiàn)時,他們就溺死 在了你的代碼中,而外面的程序全然不知,還在以一種不確定的狀態(tài)運(yùn)行著,這就可能是危險的開始。而如果是 .Net,那么 Unhandled Exception 會被虛擬機(jī)捕獲,導(dǎo)致程序異常退出,雖然這從面子上對于用戶不是一個好的交代,但是深層次地他避免了程序在危險的狀態(tài)下繼續(xù)運(yùn)行。
總之,蘿卜白菜各有所愛。我的這篇帖子力求公正地討論了這個問題,希望能對你有所幫助。