本節描述如何使用三個異常處理程序組件 — try
、catch
和finally
塊 — 來編寫異常處理程序,而後,解釋了Java SE 7中引入的try-with-resources
語句,try-with-resources
語句特別適用於使用Closeable
資源的狀況,例如流。html
本節的最後一部分將介紹一個示例,並分析各類場景中發生的狀況。java
如下示例定義並實現名爲ListOfNumbers
的類,構造時,ListOfNumbers
建立一個ArrayList
,其中包含10個具備順序值0到9的整數元素,ListOfNumbers
類還定義了一個名爲writeList
的方法,該方法將數字列表寫入名爲OutFile.txt
的文本文件中,此示例使用java.io
中定義的輸出類,這些類在基礎I/O中介紹。git
// Note: This class will not compile yet. import java.io.*; import java.util.List; import java.util.ArrayList; public class ListOfNumbers { private List<Integer> list; private static final int SIZE = 10; public ListOfNumbers () { list = new ArrayList<Integer>(SIZE); for (int i = 0; i < SIZE; i++) { list.add(new Integer(i)); } } public void writeList() { // The FileWriter constructor throws IOException, which must be caught. PrintWriter out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < SIZE; i++) { // The get(int) method throws IndexOutOfBoundsException, which must be caught. out.println("Value at: " + i + " = " + list.get(i)); } out.close(); } }
FileWriter
構造函數初始化文件上的輸出流,若是沒法打開該文件,則構造函數將拋出IOException
。對ArrayList
類的get
方法的調用,若是其參數的值過小(小於0)或太大(大於ArrayList
當前包含的元素數),則拋出IndexOutOfBoundsException
。程序員
若是你嘗試編譯ListOfNumbers類,編譯器將輸出有關FileWriter
構造函數拋出的異常的錯誤消息,可是,它不會顯示有關get
拋出的異常的錯誤消息,緣由是構造函數拋出的異常IOException
是一個通過檢查的異常,而get
方法拋出的異常(IndexOutOfBoundsException
)是一個未經檢查的異常。github
如今你已熟悉ListOfNumbers
類以及能夠在其中拋出異常的位置,你已準備好編寫異常處理程序來捕獲和處理這些異常。sql
構造異常處理程序的第一步是使用try
塊將可能引起異常的代碼括起來,一般,try
塊以下所示:segmentfault
try { code } catch and finally blocks . . .
標記爲code的示例段中包含一個或多個可能引起異常的合法代碼行(catch
和finally
塊將在接下來的兩個小節中解釋)。api
要從ListOfNumbers
類構造writeList
方法的異常處理程序,請將writeList
方法的異常拋出語句包含在try
塊中,有不止一種方法能夠作到這一點,你能夠將可能引起異常的每行代碼放在其本身的try
塊中,併爲每一個代碼提供單獨的異常處理程序。或者,你能夠將全部writeList
代碼放在一個try
塊中,並將多個處理程序與它相關聯,如下列表對整個方法使用一個try
塊,由於所討論的代碼很是短。oracle
private List<Integer> list; private static final int SIZE = 10; public void writeList() { PrintWriter out = null; try { System.out.println("Entered try statement"); out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < SIZE; i++) { out.println("Value at: " + i + " = " + list.get(i)); } } catch and finally blocks . . . }
若是try
塊中發生異常,則該異常由與之關聯的異常處理程序處理,要將異常處理程序與try
塊關聯,必須在其後面放置一個catch
塊。函數
經過在try
塊以後直接提供一個或多個catch
塊,能夠將異常處理程序與try
塊關聯,try
塊的末尾和第一個catch
塊的開頭之間不能有代碼。
try { } catch (ExceptionType name) { } catch (ExceptionType name) { }
每一個catch
塊都是一個異常處理程序,它處理由其參數表示的異常類型,參數類型ExceptionType
聲明瞭處理程序能夠處理的異常類型,而且必須是從Throwable
類繼承的類的名稱,處理程序可使用name
引用異常。
catch
塊包含在調用異常處理程序時執行的代碼,當處理程序是調用堆棧中的第一個ExceptionType
與拋出的異常類型匹配時,運行時系統調用此異常處理程序,若是拋出的對象能夠合法地分配給異常處理程序的參數,則系統認爲它是匹配的。
如下是writeList
方法的兩個異常處理程序:
try { } catch (IndexOutOfBoundsException e) { System.err.println("IndexOutOfBoundsException: " + e.getMessage()); } catch (IOException e) { System.err.println("Caught IOException: " + e.getMessage()); }
異常處理程序不只能夠打印錯誤消息或中止程序,它們能夠執行錯誤恢復、提示用戶作出決定,或使用鏈式異常將錯誤傳播到更高級別的處理程序,如「鏈式異常」部分所述。
在Java SE 7及更高版本中,單個catch
塊能夠處理多種類型的異常,此功能能夠減小代碼重複並減小捕獲過於寬泛的異常的誘惑。
在catch
子句中,指定塊能夠處理的異常類型,並使用豎線(|
)分隔每一個異常類型:
catch (IOException|SQLException ex) { logger.log(ex); throw ex; }
注意:若是catch
塊處理多個異常類型,則catch
參數隱式爲final
,在此示例中,catch
參數ex
是final
,所以你沒法在catch
塊中爲其分配任何值。
當try
塊退出時,finally
塊老是執行,這確保即便發生意外異常也會執行finally
塊,但finally
不只僅是異常處理有用 — 它容許程序員避免因return
、continue
或break
而意外繞過清理代碼,將清理代碼放在finally
塊中始終是一種很好的作法,即便沒有預期的異常狀況也是如此。
注意:若是在執行try
或catch
代碼時JVM退出,則finally
塊可能沒法執行,一樣,若是執行try
或catch
代碼的線程被中斷或終止,則即便應用程序做爲一個總體繼續,finally
塊也可能沒法執行。
你在此處使用的writeList
方法的try
塊打開了PrintWriter
,程序應該在退出writeList
方法以前關閉該流,這帶來了一個有點複雜的問題,由於writeList
的try
塊能夠以三種方式之一退出。
new FileWriter
語句失敗並拋出IOException
。list.get(i)
語句失敗並拋出IndexOutOfBoundsException
。try
塊正常退出。不管try
塊中發生了什麼,運行時系統老是執行finally
塊中的語句,因此這是進行清理的最佳地點。
下面的writeList
方法的finally
塊清理而後關閉PrintWriter
。
finally { if (out != null) { System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); } }
重要提示:finally
塊是防止資源泄漏的關鍵工具,關閉文件或以其餘方式恢復資源時,將代碼放在finally
塊中以確保始終恢復資源。請考慮在這些狀況下使用
try-with-resources
語句,這會在再也不須要時自動釋放系統資源,try-with-resources
語句部分提供了更多信息。
try-with-resources
語句是一個聲明一個或多個資源的try
語句,資源是在程序完成後必須關閉的對象,try-with-resources
語句確保在語句結束時關閉每一個資源,實現java.lang.AutoCloseable
的任何對象(包括實現java.io.Closeable
的全部對象)均可以用做資源。
如下示例從文件中讀取第一行,它使用BufferedReader
實例從文件中讀取數據,BufferedReader
是一個在程序完成後必須關閉的資源:
static String readFirstLineFromFile(String path) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader(path))) { return br.readLine(); } }
在此示例中,try-with-resources
語句中聲明的資源是BufferedReader
,聲明語句出如今try
關鍵字後面的括號內,Java SE 7及更高版本中的BufferedReader
類實現了java.lang.AutoCloseable
接口,由於BufferedReader
實例是在try-with-resource
語句中聲明的,因此不管try
語句是正常完成仍是忽然完成(因爲BufferedReader.readLine
方法拋出IOException
),它都將被關閉。
在Java SE 7以前,你可使用finally
塊來確保關閉資源,不管try
語句是正常仍是忽然完成,如下示例使用finally
塊而不是try-with-resources
語句:
static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException { BufferedReader br = new BufferedReader(new FileReader(path)); try { return br.readLine(); } finally { if (br != null) br.close(); } }
可是,在此示例中,若是方法readLine
和close
都拋出異常,則方法readFirstLineFromFileWithFinallyBlock
拋出finally
塊拋出的異常,從try
塊拋出的異常被抑制。相反,在示例readFirstLineFromFile
中,若是從try
塊和try-with-resources
語句拋出異常,則readFirstLineFromFile
方法拋出try
塊拋出的異常,從try-with-resources
塊拋出的異常被抑制,在Java SE 7及更高版本中,你能夠檢索已抑制的異常,有關詳細信息,請參閱「抑制的異常」部分。
你能夠在try-with-resources
語句中聲明一個或多個資源,如下示例檢索zip文件zipFileName中打包的文件的名稱,並建立包含這些文件名稱的文本文件:
public static void writeToFileZipFileContents(String zipFileName, String outputFileName) throws java.io.IOException { java.nio.charset.Charset charset = java.nio.charset.StandardCharsets.US_ASCII; java.nio.file.Path outputFilePath = java.nio.file.Paths.get(outputFileName); // Open zip file and create output file with // try-with-resources statement try ( java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName); java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset) ) { // Enumerate each entry for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements();) { // Get the entry name and write it to the output file String newLine = System.getProperty("line.separator"); String zipEntryName = ((java.util.zip.ZipEntry)entries.nextElement()).getName() + newLine; writer.write(zipEntryName, 0, zipEntryName.length()); } } }
在此示例中,try-with-resources
語句包含兩個以分號分隔的聲明:ZipFile
和BufferedWriter
,當直接跟隨它的代碼塊正常或因爲異常而終止時,將按此順序自動調用BufferedWriter
和ZipFile
對象的close
方法,請注意,資源的close
方法按其建立的相反順序調用。
如下示例使用try-with-resources
語句自動關閉java.sql.Statement
對象:
public static void viewTable(Connection con) throws SQLException { String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES"; try (Statement stmt = con.createStatement()) { ResultSet rs = stmt.executeQuery(query); while (rs.next()) { String coffeeName = rs.getString("COF_NAME"); int supplierID = rs.getInt("SUP_ID"); float price = rs.getFloat("PRICE"); int sales = rs.getInt("SALES"); int total = rs.getInt("TOTAL"); System.out.println(coffeeName + ", " + supplierID + ", " + price + ", " + sales + ", " + total); } } catch (SQLException e) { JDBCTutorialUtilities.printSQLException(e); } }
此示例中使用的資源java.sql.Statement
是JDBC 4.1及更高版本API的一部分。
注意:try-with-resources
語句能夠像普通的try
語句同樣有catch
和finally
塊,在try-with-resources
語句中,在聲明的資源關閉後運行任何catch
或finally
塊。
能夠從與try-with-resources
語句關聯的代碼塊中拋出異常,在示例writeToFileZipFileContents
中,能夠從try
塊拋出異常,當try-with-resources
語句嘗試關閉ZipFile
和BufferedWriter
對象時,最多能夠拋出兩個異常。若是從try
塊拋出異常,而且從try-with-resources
語句中拋出一個或多個異常,則會抑制從try-with-resources
語句拋出的那些異常,而且塊拋出的異常是writeToFileZipFileContents
方法拋出的異常,你能夠經過從try
塊拋出的異常中調用Throwable.getSuppressed
方法來檢索這些抑制的異常。
請參閱AutoCloseable和Closeable接口的Javadoc,以獲取實現這些接口之一的類列表,Closeable
接口擴展了AutoCloseable
接口。Closeable
接口的close
方法拋出IOException
類型的異常,而AutoCloseable
接口的close
方法拋出異常類型Exception
,所以,AutoCloseable
接口的子類能夠覆蓋close
方法的這種行爲,以拋出專門的異常,例如IOException
,或者根本沒有異常。
前面的部分描述瞭如何爲ListOfNumbers
類中的writeList
方法構造try
、catch
和finally
代碼塊,如今,讓咱們來看看代碼並調查會發生什麼。
將全部組件放在一塊兒時,writeList
方法以下所示。
public void writeList() { PrintWriter out = null; try { System.out.println("Entering" + " try statement"); out = new PrintWriter(new FileWriter("OutFile.txt")); for (int i = 0; i < SIZE; i++) { out.println("Value at: " + i + " = " + list.get(i)); } } catch (IndexOutOfBoundsException e) { System.err.println("Caught IndexOutOfBoundsException: " + e.getMessage()); } catch (IOException e) { System.err.println("Caught IOException: " + e.getMessage()); } finally { if (out != null) { System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); } } }
如前所述,此方法的try
塊有三種不一樣的退出可能性,這是其中兩個。
try
語句中的代碼失敗並引起異常,這多是由new FileWriter
語句引發的IOException
或由for
循環中的錯誤索引值引發的IndexOutOfBoundsException
。try
語句正常退出。建立FileWriter
的語句可能因爲多種緣由而失敗,例如,若是程序沒法建立或寫入指示的文件,則FileWriter
的構造函數將拋出IOException
。
當FileWriter
拋出IOException
時,運行時系統當即中止執行try
塊,正在執行的方法調用未完成,而後,運行時系統開始在方法調用堆棧的頂部搜索適當的異常處理程序。在此示例中,發生IOException
時,FileWriter
構造函數位於調用堆棧的頂部,可是,FileWriter
構造函數沒有適當的異常處理程序,所以運行時系統在方法調用堆棧中檢查下一個方法 — writeList
方法,writeList
方法有兩個異常處理程序:一個用於IOException
,另外一個用於IndexOutOfBoundsException
。
運行時系統按照它們在try
語句以後出現的順序檢查writeList
的處理程序,第一個異常處理程序的參數是IndexOutOfBoundsException
,這與拋出的異常類型不匹配,所以運行時系統會檢查下一個異常處理程序 — IOException
,這與拋出的異常類型相匹配,所以運行時系統結束搜索適當的異常處理程序,既然運行時已找到適當的處理程序,那麼執行該catch
塊中的代碼。
異常處理程序執行後,運行時系統將控制權傳遞給finally
塊,不管上面捕獲的異常如何,finally
塊中的代碼都會執行,在這種狀況下,FileWriter
從未打開過,不須要關閉,在finally
塊完成執行後,程序繼續執行finally
塊以後的第一個語句。
這是拋出IOException
時出現的ListOfNumbers
程序的完整輸出。
Entering try statement Caught IOException: OutFile.txt PrintWriter not open
如下清單中的後加*號的代碼顯示了在此方案中執行的語句:
public void writeList() { PrintWriter out = null; //****** try { System.out.println("Entering try statement"); //******* out = new PrintWriter(new FileWriter("OutFile.txt")); //****** for (int i = 0; i < SIZE; i++) out.println("Value at: " + i + " = " + list.get(i)); } catch (IndexOutOfBoundsException e) { System.err.println("Caught IndexOutOfBoundsException: " + e.getMessage()); } catch (IOException e) { System.err.println("Caught IOException: " + e.getMessage()); //***** } finally { if (out != null) { //***** System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); /****** } } }
在這種狀況下,try
塊範圍內的全部語句都成功執行,而且不會拋出異常,執行在try
塊的末尾結束,運行時系統將控制傳遞給finally
塊,由於一切都成功了,因此當控制到達finally
塊時,PrintWriter
會打開,這會關閉PrintWriter
,一樣,在finally
塊完成執行以後,程序繼續執行finally
塊以後的第一個語句。
當沒有拋出異常時,這是ListOfNumbers
程序的輸出。
Entering try statement Closing PrintWriter
如下示例中的加*號的代碼顯示了在此方案中執行的語句。
public void writeList() { PrintWriter out = null; //***** try { System.out.println("Entering try statement"); //****** out = new PrintWriter(new FileWriter("OutFile.txt")); //****** for (int i = 0; i < SIZE; i++) //****** out.println("Value at: " + i + " = " + list.get(i)); //****** } catch (IndexOutOfBoundsException e) { System.err.println("Caught IndexOutOfBoundsException: " + e.getMessage()); } catch (IOException e) { System.err.println("Caught IOException: " + e.getMessage()); } finally { if (out != null) { //****** System.out.println("Closing PrintWriter"); //****** out.close(); //******* } else { System.out.println("PrintWriter not open"); } } }