1. 程式人生 > >java異常——捕獲異常+再次拋出異常與異常鏈

java異常——捕獲異常+再次拋出異常與異常鏈

init fileinput 代碼塊 演示 建議 實例 ssa ack 拋出異常

【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子句中可以拋出一個異常, 這樣做的目的是 改變異常類型;

  • 3.1.1)看個荔枝:
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異常——捕獲異常+再次拋出異常與異常鏈