標籤:不同 com code cat ase ogg lex 類型 unknown
【0】README
0.1) 本文描述+原始碼均 轉自 core java volume 1, 旨在理解 java異常——捕獲異常+再次拋出異常與異常鏈 的相關知識;
【1】捕獲異常相關
1.1)如果某個異常發生的時候沒有再任何地方進行捕獲, 那程式就會運行終止: 並在控制台上列印出異常資訊 , 其中包括異常的類型堆棧的內容;
1.2)要想捕獲一個異常, 必須設定 try/catch 語句塊:
- 1.2.1)如果在try語句塊中拋出了一個在 catch子句中聲明的異常類, 那麼
- case1)程式將跳過try 語句塊的其餘代碼;
- case2)程式將執行 catch 子句中 的處理器代碼;
- 1.2.2)如果在try語句塊中沒有拋出任何異常, 那麼程式將跳過 catch子句;
- 1.2.3)如果方法中的任何代碼拋出了一個在 catch 子句中沒有聲明的異常類型, 那麼這個方法就會立刻退出;
1.3)看個荔枝: (看一個讀取文本的程式碼以示範捕獲異常的處理過程)
public void read(String filename){ try { InputStream in = new FileInputStream(filename); // 建立輸入資料流 int b; while((b=in.read()) != -1) process input } catch(IOException exception) { exception.printStackTrace(); // 列印棧軌跡; }}
對上述代碼的分析(Analysis):
- A1)需要注意的是, try 語句中的大多數代碼都很容易理解, 讀取並處理文本行, 直到遇到檔案結束符為止;(read 方法可能拋出一個IOException異常)
- A2)在這種情況下, 將跳出整個while迴圈, 進入 catch子句, 並產生一個 棧軌跡;
1.4)對於一個普通 的程式來說, 處理以上的對異常處理的方法外,還有其他方法嗎?
- 1.4.1)通常, 最好的選擇是: 什麼也不做, 而是將異常傳遞給調用者;如果read方法出現了錯誤, 那就讓read方法的調用者去處理。
- 1.4.2)如果採用這種方式, 就必須聲明這個方法可能拋出一個 IOException(將異常傳遞給調用者);
public void read(String filename) throws IOException{ InputStream in = new FileInputStream(filename); // 建立輸入資料流 int b; while((b=in.read()) != -1) process input}
Attention)編譯器嚴格地執行 throws 說明符。 如果調用了一個拋出已檢查異常的方法, 就必須對它進行處理, 或者將它繼續進行傳遞;
1.5)對於以上兩種處理異常的方法, 哪種 方法更好呢?(method1:自己處理(在可能發生異常的函數中添加try/catch 語句塊);method2:將異常傳遞(throw)給調用者,調用者處理)
- 1.5.1)通常, 應該捕獲那些知道如何處理的異常, 而將那些不知道怎麼處理的異常繼續進行傳遞;如果想傳遞一個異常, 就必須在方法的首部添加一個throws 說明符, 以便告知調用者這個方法可能會拋出異常;
- 1.5.2)閱讀API後, 以便知道這個方法可能會拋出哪種異常, 然後再決定是自己處理, 還是添加到 throws 列表中;
Attention)以上規則有個例外: 前面提到, 如果編寫一個 覆蓋超類的方法, 而這個方法又沒有拋出異常, 那麼這個方法就必須捕獲方法代碼中出現的每一個已檢查異常。不允許在子類的 throws 說明符中出現超過超類方法所列出的異常類範圍;(也就是說父類方法沒有拋出異常,你子類方法也不準拋出異常,只能自己添加 try/catch 語句塊自己處理)
【2】捕獲多個異常
2.1)在一個try 語句塊中可以捕獲多個異常, 並對不同類型的異常做出不同的處理。可以按照下列方式為每個異常類型使用一個單獨的 catch 子句;
try{}catch(FileNotFoundException e){}catch(UnknownHostException e){}catch(IOException e){}
2.2)要想獲得異常對象 的更多資訊: 可以試著使用 e.getMessage() 得到詳細的錯誤資訊, 或者使用 e.getClass().getName(); 得到異常對象 的實際類型;
2.3)合并catch 子句: 在 java SE7中, 同一個 catch 子句中可以捕獲多個異常類型。 例如, 假設對應缺少檔案和 未知主機異常的動作是一樣的, 就可以合并catch 子句:
try{}catch(FileNotFoundException | UnknownHostException e){}catch(IOException e){}
Attention)
- A1)只有當捕獲的異常類型彼此間不存在子類別關係時 才需要這個特性;
- A2)捕獲多個異常時, 異常變數隱含為 final變數。例如, 不能在以下子句體中為 e 賦不同的 值;
catch(FileNotFoundException || UnknownHostException e) {}
- A3)捕獲多個異常不僅會讓你的代碼看起來簡單, 還會更高效。產生的位元組碼只包含一個對應公用catch 子句的代碼塊;
【3】再次拋出異常與異常鏈
3.1)在catch子句中可以拋出一個異常, 這樣做的目的是 改變異常類型;
try{}catch(SQLException e){ throw new ServletException("data error : " + e.getMessage());}
對以上代碼的分析(Analysis):
- A1)這裡, ServletException 用帶有異常資訊文本的構造器來構造;
- A2)不過, 可以有一種更好的方法, 並且將原始異常設定為新異常的原因:
try{}catch(SQLException e){ Throwable se = new ServletException("database error"); se.initCause(e); throw se;}
- A3)當捕獲到這個異常時, 就可以使用下面的語句重新得到 原始異常:
Throwable e = se.getCause();
Attention)強烈建議使用這種封裝技術, 這樣可以讓使用者拋出子系統中的進階異常, 而不會丟失原始異常的小細節; (推薦使用 strongly recommended)
Hint)
- H1)如果在一個方法中發生了一個已檢查異常,而不允許拋出它, 那麼封裝技術就十分有用。 我們還可以捕獲這個已檢查異常, 並將它封裝成一個 運行時異常;
- H2)有時候, 你可能只想記錄一個異常,再將它重新拋出, 而不做任何改變:
try{ access the database}catch(Exception e){ logger.log(level, message, e); throw e;}
- H3)在Java SE7 之前, 將上述代碼放入下述方法中, 會出現一個問題;
public void updateRecord() throws SQLException
- 因為, java 編譯器查看catch 塊中的 throw 語句, 然後查看e的類型, 會指出這個方法可以拋出任何Exception而不僅僅是 SQLException;
- H4)java se 7之後(編譯器檢測文法合法): 編譯器會跟蹤到 e 來自於try塊中, 假設這個 try 塊中僅有 的已檢查異常 是 SQLException執行個體, 另外,假設e在catch塊中未改變, 將外圍方法聲明為 throws SQLException 就是合法的;
java異常——捕獲異常+再次拋出異常與異常鏈