標籤:finally 調用 好的 用法 強制 not javascrip 列印 add
1.異常的限制
當覆蓋方法的時候,只能拋出在基類方法的異常說明裡列出的那些異常。這意味著,當基類使用的代碼應用到其衍生類別對象的時候,一樣能夠工資,異常也不例外。
下面的例子是在編譯時間施加在異常上面的限制:
public class BaseBallException extends Exception {}public class Foul extends BaseBallException{}public class Strike extends BaseBallException{}public abstract class Inning { public Inning() throws BaseBallException{} public void event() throws BaseBallException{} public abstract void addBat()throws Strike,Foul; public void walk() {}}public class StormException extends Exception{}public class RainedOut extends StormException{}public class PopFoul extends Foul{}public interface Storm { public void event() throws RainedOut; public void rainHard() throws RainedOut;}public class StromMyInnerings extends Inning implements Storm{ /** * @throws BaseBallException */ public StromMyInnerings() throws RainedOut,BaseBallException {} public StromMyInnerings(String s)throws RainedOut,BaseBallException{} /** * @throws RainedOut */ @Override public void rainHard() throws RainedOut {} /** * @throws Strike */ @Override public void addBat() throws PopFoul {} public void event(){} public static void main(String[] args) { StromMyInnerings stromMyInnerings; try { stromMyInnerings = new StromMyInnerings(); stromMyInnerings.addBat(); } catch (RainedOut e) { System.out.println("RainedOut"); } catch (PopFoul e) { System.out.println("PopFoul"); } catch (BaseBallException e) { System.out.println("BaseBallException"); } Inning inning; try { inning = new StromMyInnerings(); inning.addBat(); } catch (Strike e) { System.out.println("Strike"); } catch (Foul e) { System.out.println("Foul"); } catch (RainedOut e) { System.out.println("RainedOut"); } catch (BaseBallException e) { System.out.println("BaseBallException"); } }}
在Inning類中,可以看到構造器和event()方法都聲明將拋出異常,但實際上沒有拋出。這種方式使你能強制使用者去捕獲可能在覆蓋後的event()版本中增加的異常,所以它很合理。這對於抽象方法同樣成立,如addBat()。
介面Storm值得注意,因為它包含了一個在Inning中定義的方法event()和一個不在Inning中定義的方法rainHard()。這兩個方法都拋出新的異常RainedOut。如果StromMyInnerings類在擴充Inning類的同事又出現了Storm介面,那麼Storm裡的event()方法就不能改變Inning中的event()方法的異常介面。否則的話,在使用基類的時候就不能判斷是否捕獲了正常的異常。當然,如果介面裡定義的方法不是來自於基類,比如rainHard(),那麼此方法拋出什麼樣的異常都沒有問題。
異常限制對構造器不起作用,你會發現StromMyInnerings的構造器可以拋出任何異常,而不必理會基類構造器所拋出的異常。衍生類別構造器不能捕獲基類構造器拋出的異常。
儘管在繼承過程中,編譯器會對異常說明做強制要求,但異常說明本身並不屬於方法類型的一部分,方法類型是由方法的名字與參數的類型組成的。因此,不能基於異常說明來重載方法。此外,一個出現在基類方法的異常,不一定會出現在衍生類別方法的異常說明裡。換句話說,在繼承和覆蓋的過程中,某個特定方法的“異常說明的介面”不是變大了而是變小了–這恰好和類介面在繼承時的情形相反。
2.構造器
如果一個類繼承了某個類同時又實現了某個介面,他們有同樣的介面方法,但都拋出了不同的捕獲性異常,則該子類實現與重寫該方法時,則方法聲明處不能拋出任何捕獲性異常了。如果調用的父類構造器拋出捕獲性異常,則子類相應的構造器也只能拋出,不能在構造器裡進行捕獲。構造器拋出異常時正確的清理方式:比如在構造器中開啟了一個檔案,清理動作只有在對象使用完畢並且使用者調用了特殊的清理方法之後才能得以清理,而不能直接在構造器裡的finally塊上關閉,因為finally塊是不管是否有異常都會關閉,而構造器執行成功能外界需要這個檔案流。但如果在檔案成功開啟後才拋出異常,則需要關閉檔案,並向外界拋出異常資訊:
public class InputFile { private BufferedReader in; public InputFile(String fname) throws Exception{ try { in = new BufferedReader(new FileReader(fname)); } catch (FileNotFoundException e) { System.out.println("could not open "+fname); throw e; }catch (Exception e) { try { in.close(); } catch (IOException e1) { System.out.println("in.close() unsuccessful"); throw e; }finally{ } } } public String getLine(){ String s; try { s=in.readLine(); } catch (IOException e) { throw new RuntimeException("readLine() failed"); } return s; } public void dispose(){ try { in.close(); System.out.println("dispose() successful"); } catch (IOException e) { throw new RuntimeException("in.close() failed"); }}public class CleanUp { public static void main(String[] args) { try { InputFile inputFile = new InputFile("D:/workspace/testjava/src/com/test/CleanUp.java"); try { String s; int i = 1; while ((s = inputFile.getLine()) != null) { } } catch (Exception e) { System.out.println("caught Exception in main"); e.printStackTrace(System.out); }finally{ inputFile.dispose(); } } catch (Exception e) { System.out.println("InputFile construction failed"); } }}
InputFile 的構造器接受字串作為參數,該字串表示所要開啟的檔案名稱。在try塊中,會使用此檔案名稱建立了FileReader對象。FileReader對象本身用處並不大,但可以用它來建立BufferedReader對象。注意,使用InputFile 的好處就是把兩步操作合二為一。這種通用的清理習慣用法在構造器不拋出任何異常時也應該運用,其基本規則是:在建立需要清理的對象之後,立即進入一個try-finally語句塊。
3.異常匹配
拋出異常的時候,異常處理系統會按照代碼的書寫順序找出“最近”的處理常式。找到匹配的處理常式之後,它就認為異常將得到處理,然後就不再繼續尋找。
尋找的時候並不要求拋出的例外處理常式所聲明的異常完全符合。衍生類別的對象也可以匹配其基類的處理常式,例子如下:
public class Annoyance extends Exception{}public class Sneeze extends Annoyance{}public class Human { public static void main(String[] args) { try { throw new Sneeze(); } catch (Sneeze e) { System.out.println("catch Sneeze"); }catch (Annoyance e) { System.out.println("catch Annoyance"); } try { throw new Sneeze(); }catch (Annoyance e) { System.out.println("catch Annoyance exception"); } }}
Sneeze異常會被第一個匹配的catch自居補貨,也就是程式裡的第一個。然而如果將這個catch字句刪掉,只留下Annoyance的catch子句,該橫向仍然能正常運行,因為這次補貨的是Sneeze的基類。換句話說,catch(Annoyance e)會捕獲Annoyance以及所有從它派生的異常。
如果把捕獲基類的catch子句放在最前面,以此想把衍生類別的異常全給“屏蔽”掉,就像這樣:
try { throw new Sneeze(); }catch (Annoyance e) { //... } catch (Sneeze e) { //..... }
這樣編譯器就會發現Sneeze的catch子句永遠也得不到執行,因此會報錯。
4.其他的可選方式
異常處理的一個重要的原則是“只有在你知道如何處理的情況下才捕獲異常”。實際上,異常處理的一個重要目標就是把錯誤處理的代碼同錯誤發生的地點相分離。這使你能在一段代碼中專註於要完成的事情,至於如何處理錯誤,則放在另一段代碼中。這樣以來,主幹代碼就不會與錯誤處理邏輯混在一起,也更容易理解和維護。
“被檢查的異常”可能使問題變得複雜,因為它們強制你在可能還沒有準備好處理錯誤的時候被迫加上cacth子名,即使我們不知道如何處理的情況下,這就導致了異常的隱藏:
try{ //… throw …}catch(Exception e){//什麼都不做}
把“被檢查的異常”轉換為“不檢查的異常”:當在一個普通方法裡調用別的方法時,要考慮到“我不知道該怎樣處理這個異常,但是也不能把它‘吞’了,或者只列印一些無用的訊息”。JDK1.4的異常鏈提供了一種新的思路來解決這個問題,可以直接把“被檢查的異常”封裝進RuntimeException裡面:
try{ //… throw 檢查異常…}catch(IDontKnowWhatToDoWithThisCheckedException e){ Throw new RuntimeException(e);}
如果想把“被檢查的異常”這種功能“屏蔽”掉的話,上面是一個好的辦法。不用“吞”掉異常,也不必把它放到方法的異常聲明裡面,而異常鏈還能保證你不會丟失任何原始異常的資訊。你還可以在“知道如何處理的地方”來處理它,也可以其他上層catch裡通過 throw e.getCause(); 再次拋出原始的“被檢查的異常”。
5.異常使用指南
應該在以下情況使用異常:
1)在恰當的層級處理問題。(在知道該如何處理的情況下才捕獲異常。)
2)解決問題並且重新調用產生異常的方法。
3)進行少許修補,然後繞過異常產生的地方繼續執行。
4)用別的資料進行計算,以代替方法預計會返回的值。
5)把當前運行環境下能做的事情盡量做完,然後把相同的異常重拋到更高層。
6)把當前運行環境下能做的事情盡量做完,然後把不同的異常拋到更高層。
7)終止程式。
8)進行簡化。
9)讓類庫和程式更安全。
6.總結
異常時java程式設計不可分割的一部分,如果不瞭解 如何使用它們,那你只能完成很有限的工作。異常處理的有點之一就是它使得你可以在某處集中精力處理你要解決的問題,而在另一邊處理你編寫的這段代碼中產生的錯誤。
java編程思想讀書筆記 第十二章 通過異常處理錯誤(下)