這篇和前面的 總結java的interface和abstract class 一樣。跳過最基礎語法不聊,只說一些比較tricky的東西和一些好的practice.
語法:
Exception繼承自Throwable. Throwable還有一個兒子是Error,但是一般用不到。不過有些二百五面試官喜歡問回字有幾種寫法,所以還是要知道有Error這回事的。Exception分為checked和unchecked兩種。
java的checked exception一直是個很有爭議的東西。Thinking in Java的作者Bruce, C#的設計者和Martin Fowler都對checked exception持質疑態度。 一個典型的反對理由是:"We felt it was unrealistic to require the programmer to provide handlers in situations where no meaningful action can be taken." 這話太實惠了。寫方法給caller調用,怎么能magically知道調用這個方法的所有caller,能夠處理特定的異常呢?
關于Exception有個比較基本的語法是,子類override的方法如果聲明拋出exception, 只能拋出父方法聲明的exception,或者那個exception的子類。需要注意的是,“回”字還有一種寫法,對于constructor來說沒有這個限制,子類可以拋出任意exception。父類構造函數聲明的exception,子類也必須聲明,而且子類的構造函數不能捕捉父類聲明的exception. 這個想想也容易理解,父類構造出錯了,兒子居然能處理還把自己生出來了,沒老子哪來的兒子?當然你可以抬杠說老子戴綠帽子的情況。
實踐:
1. 盡量不要在構造函數里做復雜的操作,盡量不要讓constructor拋出exception。如果在構造函數里拋出exception,需要用nested try block. 如下:
1: public class Cleanup {
2: public static void main(String[] args) {
3: try {
4: InputFile in = new InputFile("Cleanup.java");
5: try {
6: String s;
7: int i = 1;
8: while((s = in.getLine()) != null)
9: ; // Perform line-by-line processing here...
10: } catch(Exception e) {
11: System.out.println("Caught Exception in main");
12: e.printStackTrace(System.out);
13: } finally {
14: in.dispose();
15: }
16: } catch(Exception e) {
17: System.out.println("InputFile construction failed");
18: }
19: }
20: }
而不是用finally來做清理工作。
2. exception的一個基本使用原則是,exception不是設計用來控制程序flow的。 這是很簡單的道理,還是引用effective java的一個例子吧
1: // Horrible abuse of exceptions. Don't ever do this!
2: try {
3: int i = 0;
4: while(true)
5: range[i++].climb();
6: } catch(ArrayIndexOutOfBoundsException e) {
7: }
我真正要說明的是,上面說的原則很對,但是走到極端就不對了。有的人為了 不用exception控制程序flow, 就寫一大堆的if…else語句試圖考慮各種情況,正好前不久有同事說了個笑話,我覺得可以輔助解釋這個問題。
=============================
某日,老師在課堂上想考考學生們的智商,就問一個男孩“樹上有十只鳥,開槍打死一只,還剩幾只?”
男孩反問“是無聲手槍,還是其他沒有聲音的槍么?”
“不是。”
“槍聲有多大?”
“80~100分貝。”
“那就是說會震的耳朵疼?”
“是。”
“在這個城市里打鳥犯不犯法?”
“不犯。”
“您確定那只鳥真的被打死啦?”
“確定.”老師已經不耐煩了,”拜托,你告訴我還剩幾只就行了,OK?”
“OK.鳥里有沒有聾子?”
“沒有。”
“有沒有鳥智力有問題,呆傻到聽到槍響不知道飛的?”
“沒有,智商都在200以上!”
“有沒有關在籠子里的?”
“沒有。”
。。。
==============================
后面還有一堆“例外”情況。我們寫程序總不能真寫成
if(鳥是聾子)
…
else if(鳥是傻子)
…
else if(鳥是瘸子)
…
原則應該是,如果一些情況確實是 “例外情況”,就用exception處理吧。不要很勤奮地寫一堆defensive的判斷。我們不會有故事里的小男孩兒思維那么滴水不漏的。別把Java程序退回c語言了。另一個例子是FileNotFoundException, java I/O沒有讓你每次用文件都提前調用exists()檢查一下,我想原因不光是 檢查文件的那一毫秒文件存在,run到下一步的時候,下一毫秒文件消失了,Sun沒覺得你人品那么壞吧。理念仍然是,如果你覺得文件肯定存在,你就直接用吧,一旦不存在,你再另外當成異常情況處理。不要讓一堆if…else弄臟了程序。
3. exception有個典型用法是在方法體中,進行參數合法性校驗
1: public BigInteger mod(BigInteger m) {
2: if (m.signum() <= 0)
3: throw new ArithmeticException("Modulus <= 0: " + m);
4: ... // Do the computation
5: }
也有很多人用assert語句判斷, 比如 Assert.notNull(object)。手動拋exception可以拋特定的類型,assert語句更方便。可以根據實際情況取舍。
4. 既然上面說了checked exception本身是java設計不太合理的地方。我傾向于說,應該及時把checked exception translate成unchecked. 我知道exception的處理原則有一條是,如果你不知道怎么處理它,就不要捕捉它。 對于checked exception來說,它總force你去處理,太討厭了。如果caller不知道怎么處理,留著給更上層的程序處理…底層的程序都不會處理,一般來說上層的程序就更不知道該怎么處理了,那還不如在盡量底層的調用中,要么處理它(這種情況很少,打log之類的算不上“處理”),要么就轉成RuntimeException拋上來, 消滅掉checked exception帶來的burden. 注意:translate exception的過程中,不要扔了原來的exception, 而要把它放在exception constructor的argument里, new RuntimeException(e)。這是很基本的東西,不多說。
5. 不要吞exception. 這個太基礎,不多說。
6. 見到exception要把它記log里,而不是簡單print stack一下,log4j的api有可以接受Throwable作為參數的。
7. 每層拋出來的exception要對當前這一層有意義,比如persistence層出問題,UI上你告訴客戶hibernate的session關閉了,不能繼續load數據了,客戶還以為你的程序怎么跟狗熊一樣還會冬眠的。即使是UI層以下,底層exception,比如sql exception也不要爬到domain層里處理
8. apache commons的lang包里有ExceptionUtils類,玩兒exception最好把這個工具揣口袋里。
9.exception是設計的一部分, 但它不同于API的設計。通常我們設計API的時候,不會設計一個函數destroyBaghdad(),通常我們會寫destroyCity(Baghdad)。這樣做的目的是為了重用。換句話說,你設計API的時候,總是裝作忘了use case(caller), 而去寫適合復用的API, 盡管上面的例子use case就是destroy Baghdad, 你還是要寫更general的destroyCity函數,然后把城市的名字作為參數傳進去.但exception的設計不應該用同樣的思路做,因為你很難料想到復用的情況下,你聲明的exception是不是總能在任何情況下都得到妥善的處理。舉個例子,這是我臨時想的例子,形象但是科技太超前了。我們寫一個print()程序給打印機A,print()的時候沒有紙可以拋一個checked NoPaperException,這時候exceptino的處理程序可以自動加載紙(目前這么高級的功能正在貝爾實驗室研發呢)。也許有新型的打印機B總是先加紙,后打印,那么永遠也不存在NoPaperException。如果打印機是老式打印機C,不會自動加載紙,見到NoPaterException也無計可施,沒法處理。checked exception的哲學是,強制讓caller處理它。從上面的例子看,只有A打印機需要并能夠處理NoPaperException。 B打印機不需要處理exception。C打印機沒能力處理exception. 所以,|“需要并能夠”處理是個太嚴格的限制,一般情況下不應該用checked exception. 我們可以讓print聲明拋出unchecked exception. 提醒caller可以處理它,但是對于不應該處理它的caller也不強制去處理它。