捕獲和處理異常
本節(jié)描述如何使用三個異常處理程序組件 — try、catch和finally塊 — 來編寫異常處理程序,然后,解釋了Java SE 7中引入的try-with-resources語句,try-with-resources語句特別適用于使用Closeable資源的情況,例如流。
本節(jié)的最后一部分將介紹一個示例,并分析各種場景中發(fā)生的情況。
以下示例定義并實現(xiàn)名為ListOfNumbers的類,構(gòu)造時,ListOfNumbers創(chuàng)建一個ArrayList,其中包含10個具有順序值0到9的整數(shù)元素,ListOfNumbers類還定義了一個名為writeList的方法,該方法將數(shù)字列表寫入名為OutFile.txt的文本文件中,此示例使用java.io中定義的輸出類,這些類在基礎(chǔ)I/O中介紹。
// Note: This class will not compile yet. import java.io.*; import java.util.List; import java.util.ArrayList; public class ListOfNumbers { private Listlist; private static final int SIZE = 10; public ListOfNumbers () { list = new ArrayList (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構(gòu)造函數(shù)初始化文件上的輸出流,如果無法打開該文件,則構(gòu)造函數(shù)將拋出IOException。對ArrayList類的get方法的調(diào)用,如果其參數(shù)的值太?。ㄐ∮?)或太大(大于ArrayList當(dāng)前包含的元素數(shù)),則拋出IndexOutOfBoundsException。
如果你嘗試編譯ListOfNumbers類,編譯器將輸出有關(guān)FileWriter構(gòu)造函數(shù)拋出的異常的錯誤消息,但是,它不會顯示有關(guān)get拋出的異常的錯誤消息,原因是構(gòu)造函數(shù)拋出的異常IOException是一個經(jīng)過檢查的異常,而get方法拋出的異常(IndexOutOfBoundsException)是一個未經(jīng)檢查的異常。
現(xiàn)在你已熟悉ListOfNumbers類以及可以在其中拋出異常的位置,你已準(zhǔn)備好編寫異常處理程序來捕獲和處理這些異常。
try塊構(gòu)造異常處理程序的第一步是使用try塊將可能引發(fā)異常的代碼括起來,通常,try塊如下所示:
try { code } catch and finally blocks . . .
標(biāo)記為code的示例段中包含一個或多個可能引發(fā)異常的合法代碼行(catch和finally塊將在接下來的兩個小節(jié)中解釋)。
要從ListOfNumbers類構(gòu)造writeList方法的異常處理程序,請將writeList方法的異常拋出語句包含在try塊中,有不止一種方法可以做到這一點,你可以將可能引發(fā)異常的每行代碼放在其自己的try塊中,并為每個代碼提供多帶帶的異常處理程序?;蛘?,你可以將所有writeList代碼放在一個try塊中,并將多個處理程序與它相關(guān)聯(lián),以下列表對整個方法使用一個try塊,因為所討論的代碼非常短。
private Listlist; 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塊中發(fā)生異常,則該異常由與之關(guān)聯(lián)的異常處理程序處理,要將異常處理程序與try塊關(guān)聯(lián),必須在其后面放置一個catch塊。
catch塊通過在try塊之后直接提供一個或多個catch塊,可以將異常處理程序與try塊關(guān)聯(lián),try塊的末尾和第一個catch塊的開頭之間不能有代碼。
try { } catch (ExceptionType name) { } catch (ExceptionType name) { }
每個catch塊都是一個異常處理程序,它處理由其參數(shù)表示的異常類型,參數(shù)類型ExceptionType聲明了處理程序可以處理的異常類型,并且必須是從Throwable類繼承的類的名稱,處理程序可以使用name引用異常。
catch塊包含在調(diào)用異常處理程序時執(zhí)行的代碼,當(dāng)處理程序是調(diào)用堆棧中的第一個ExceptionType與拋出的異常類型匹配時,運行時系統(tǒng)調(diào)用此異常處理程序,如果拋出的對象可以合法地分配給異常處理程序的參數(shù),則系統(tǒng)認(rèn)為它是匹配的。
以下是writeList方法的兩個異常處理程序:
try { } catch (IndexOutOfBoundsException e) { System.err.println("IndexOutOfBoundsException: " + e.getMessage()); } catch (IOException e) { System.err.println("Caught IOException: " + e.getMessage()); }
異常處理程序不僅可以打印錯誤消息或停止程序,它們可以執(zhí)行錯誤恢復(fù)、提示用戶做出決定,或使用鏈?zhǔn)疆惓㈠e誤傳播到更高級別的處理程序,如“鏈?zhǔn)疆惓!辈糠炙觥?/p> 使用一個異常處理程序捕獲多種類型的異常
在Java SE 7及更高版本中,單個catch塊可以處理多種類型的異常,此功能可以減少代碼重復(fù)并減少捕獲過于寬泛的異常的誘惑。
在catch子句中,指定塊可以處理的異常類型,并使用豎線(|)分隔每個異常類型:
catch (IOException|SQLException ex) { logger.log(ex); throw ex; }
注意:如果catch塊處理多個異常類型,則catch參數(shù)隱式為final,在此示例中,catch參數(shù)ex是final,因此你無法在catch塊中為其分配任何值。finally塊
當(dāng)try塊退出時,finally塊總是執(zhí)行,這確保即使發(fā)生意外異常也會執(zhí)行finally塊,但finally不僅僅是異常處理有用 — 它允許程序員避免因return、continue或break而意外繞過清理代碼,將清理代碼放在finally塊中始終是一種很好的做法,即使沒有預(yù)期的異常情況也是如此。
注意:如果在執(zhí)行try或catch代碼時JVM退出,則finally塊可能無法執(zhí)行,同樣,如果執(zhí)行try或catch代碼的線程被中斷或終止,則即使應(yīng)用程序作為一個整體繼續(xù),finally塊也可能無法執(zhí)行。
你在此處使用的writeList方法的try塊打開了PrintWriter,程序應(yīng)該在退出writeList方法之前關(guān)閉該流,這帶來了一個有點復(fù)雜的問題,因為writeList的try塊可以以三種方式之一退出。
new FileWriter語句失敗并拋出IOException。
list.get(i)語句失敗并拋出IndexOutOfBoundsException。
一切都成功,try塊正常退出。
無論try塊中發(fā)生了什么,運行時系統(tǒng)總是執(zhí)行finally塊中的語句,所以這是進(jìn)行清理的最佳地點。
下面的writeList方法的finally塊清理然后關(guān)閉PrintWriter。
finally { if (out != null) { System.out.println("Closing PrintWriter"); out.close(); } else { System.out.println("PrintWriter not open"); } }
重要提示:finally塊是防止資源泄漏的關(guān)鍵工具,關(guān)閉文件或以其他方式恢復(fù)資源時,將代碼放在finally塊中以確保始終恢復(fù)資源。try-with-resources語句請考慮在這些情況下使用try-with-resources語句,這會在不再需要時自動釋放系統(tǒng)資源,try-with-resources語句部分提供了更多信息。
try-with-resources語句是一個聲明一個或多個資源的try語句,資源是在程序完成后必須關(guān)閉的對象,try-with-resources語句確保在語句結(jié)束時關(guān)閉每個資源,實現(xiàn)java.lang.AutoCloseable的任何對象(包括實現(xiàn)java.io.Closeable的所有對象)都可以用作資源。
以下示例從文件中讀取第一行,它使用BufferedReader實例從文件中讀取數(shù)據(jù),BufferedReader是一個在程序完成后必須關(guān)閉的資源:
static String readFirstLineFromFile(String path) throws IOException { try (BufferedReader br = new BufferedReader(new FileReader(path))) { return br.readLine(); } }
在此示例中,try-with-resources語句中聲明的資源是BufferedReader,聲明語句出現(xiàn)在try關(guān)鍵字后面的括號內(nèi),Java SE 7及更高版本中的BufferedReader類實現(xiàn)了java.lang.AutoCloseable接口,因為BufferedReader實例是在try-with-resource語句中聲明的,所以無論try語句是正常完成還是突然完成(由于BufferedReader.readLine方法拋出IOException),它都將被關(guān)閉。
在Java SE 7之前,你可以使用finally塊來確保關(guān)閉資源,無論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及更高版本中,你可以檢索已抑制的異常,有關(guān)詳細(xì)信息,請參閱“抑制的異?!辈糠?。
你可以在try-with-resources語句中聲明一個或多個資源,以下示例檢索zip文件zipFileName中打包的文件的名稱,并創(chuàng)建包含這些文件名稱的文本文件:
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,當(dāng)直接跟隨它的代碼塊正常或由于異常而終止時,將按此順序自動調(diào)用BufferedWriter和ZipFile對象的close方法,請注意,資源的close方法按其創(chuàng)建的相反順序調(diào)用。
以下示例使用try-with-resources語句自動關(guān)閉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語句中,在聲明的資源關(guān)閉后運行任何catch或finally塊。抑制異常
可以從與try-with-resources語句關(guān)聯(lián)的代碼塊中拋出異常,在示例writeToFileZipFileContents中,可以從try塊拋出異常,當(dāng)try-with-resources語句嘗試關(guān)閉ZipFile和BufferedWriter對象時,最多可以拋出兩個異常。如果從try塊拋出異常,并且從try-with-resources語句中拋出一個或多個異常,則會抑制從try-with-resources語句拋出的那些異常,并且塊拋出的異常是writeToFileZipFileContents方法拋出的異常,你可以通過從try塊拋出的異常中調(diào)用Throwable.getSuppressed方法來檢索這些抑制的異常。
實現(xiàn)AutoCloseable或Closeable接口的類請參閱AutoCloseable和Closeable接口的Javadoc,以獲取實現(xiàn)這些接口之一的類列表,Closeable接口擴(kuò)展了AutoCloseable接口。Closeable接口的close方法拋出IOException類型的異常,而AutoCloseable接口的close方法拋出異常類型Exception,因此,AutoCloseable接口的子類可以覆蓋close方法的這種行為,以拋出專門的異常,例如IOException,或者根本沒有異常。
把它們放在一起前面的部分描述了如何為ListOfNumbers類中的writeList方法構(gòu)造try、catch和finally代碼塊,現(xiàn)在,讓我們來看看代碼并調(diào)查會發(fā)生什么。
將所有組件放在一起時,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語句中的代碼失敗并引發(fā)異常,這可能是由new FileWriter語句引起的IOException或由for循環(huán)中的錯誤索引值引起的IndexOutOfBoundsException。
一切都成功,try語句正常退出。
場景1:發(fā)生異常創(chuàng)建FileWriter的語句可能由于多種原因而失敗,例如,如果程序無法創(chuàng)建或?qū)懭胫甘镜奈募?,則FileWriter的構(gòu)造函數(shù)將拋出IOException。
當(dāng)FileWriter拋出IOException時,運行時系統(tǒng)立即停止執(zhí)行try塊,正在執(zhí)行的方法調(diào)用未完成,然后,運行時系統(tǒng)開始在方法調(diào)用堆棧的頂部搜索適當(dāng)?shù)漠惓L幚沓绦颉T诖耸纠?,發(fā)生IOException時,FileWriter構(gòu)造函數(shù)位于調(diào)用堆棧的頂部,但是,FileWriter構(gòu)造函數(shù)沒有適當(dāng)?shù)漠惓L幚沓绦颍虼诉\行時系統(tǒng)在方法調(diào)用堆棧中檢查下一個方法 — writeList方法,writeList方法有兩個異常處理程序:一個用于IOException,另一個用于IndexOutOfBoundsException。
運行時系統(tǒng)按照它們在try語句之后出現(xiàn)的順序檢查writeList的處理程序,第一個異常處理程序的參數(shù)是IndexOutOfBoundsException,這與拋出的異常類型不匹配,因此運行時系統(tǒng)會檢查下一個異常處理程序 — IOException,這與拋出的異常類型相匹配,因此運行時系統(tǒng)結(jié)束搜索適當(dāng)?shù)漠惓L幚沓绦?,既然運行時已找到適當(dāng)?shù)奶幚沓绦?,那么?zhí)行該catch塊中的代碼。
異常處理程序執(zhí)行后,運行時系統(tǒng)將控制權(quán)傳遞給finally塊,無論上面捕獲的異常如何,finally塊中的代碼都會執(zhí)行,在這種情況下,FileWriter從未打開過,不需要關(guān)閉,在finally塊完成執(zhí)行后,程序繼續(xù)執(zhí)行finally塊之后的第一個語句。
這是拋出IOException時出現(xiàn)的ListOfNumbers程序的完整輸出。
Entering try statement Caught IOException: OutFile.txt PrintWriter not open
以下清單中的后加*號的代碼顯示了在此方案中執(zhí)行的語句:
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"); /****** } } }場景2:try塊正常退出
在這種情況下,try塊范圍內(nèi)的所有語句都成功執(zhí)行,并且不會拋出異常,執(zhí)行在try塊的末尾結(jié)束,運行時系統(tǒng)將控制傳遞給finally塊,因為一切都成功了,所以當(dāng)控制到達(dá)finally塊時,PrintWriter會打開,這會關(guān)閉PrintWriter,同樣,在finally塊完成執(zhí)行之后,程序繼續(xù)執(zhí)行finally塊之后的第一個語句。
當(dāng)沒有拋出異常時,這是ListOfNumbers程序的輸出。
Entering try statement Closing PrintWriter
以下示例中的加*號的代碼顯示了在此方案中執(zhí)行的語句。
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"); } } }上一篇:捕獲或指定要求 下一篇:如何拋出異常
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/77482.html
捕獲或指定要求 有效的Java編程語言代碼必須遵守捕獲或指定需求,這意味著可能拋出某些異常的代碼必須包含以下任一項: 捕獲異常的try語句,try必須為異常提供處理程序,如捕獲和處理異常中所述。 一種方法,指定它可以拋出異常,該方法必須提供一個throws子句,列出異常,如通過方法拋出指定異常中所述。 不符合捕獲或指定要求的代碼將無法編譯。 并非所有異常都受捕獲或指定要求的約束,為了理解原因,...
異常的優(yōu)點 現(xiàn)在你已經(jīng)知道了什么是異常以及如何使用它們,現(xiàn)在是時候了解在程序中使用異常的優(yōu)勢了。 優(yōu)點1:將錯誤處理代碼與常規(guī)代碼分開 異常提供了從程序的主邏輯中分離異常發(fā)生時應(yīng)該做什么的細(xì)節(jié)的方法,在傳統(tǒng)的編程中,錯誤檢測、報告和處理通常會導(dǎo)致混亂的意大利面代碼,例如,考慮這里的偽代碼方法將整個文件讀入內(nèi)存。 readFile { open the file; determine...
如何拋出異常 在捕獲異常之前,某些代碼必須拋出一個,任何代碼都可能拋出異常:你的代碼,來自其他人編寫的包中的代碼,例如Java平臺附帶的包或Java運行時環(huán)境,無論拋出什么異常,它總是使用throw語句拋出。 你可能已經(jīng)注意到,Java平臺提供了許多異常類,所有類都是Throwable類的后代,并且所有類都允許程序區(qū)分在程序執(zhí)行期間可能發(fā)生的各種類型的異常。 你還可以創(chuàng)建自己的異常類來表示你編寫的...
什么是異常? exception一詞是exceptional event這一短語的簡寫。 定義:異常是在程序執(zhí)行期間發(fā)生的事件,它會破壞程序指令的正常流程。 當(dāng)方法中發(fā)生錯誤時,該方法會創(chuàng)建一個對象并將其交給運行時系統(tǒng),該對象稱為異常對象,包含有關(guān)錯誤的信息,包括錯誤發(fā)生時的類型和程序狀態(tài),創(chuàng)建異常對象并將其交給運行時系統(tǒng)稱為拋出異常。 在方法拋出異常后,運行時系統(tǒng)會嘗試查找處理它的內(nèi)容,處理異常...
Java? 教程 Java教程是為JDK 8編寫的,本頁面中描述的示例和實踐沒有利用在后續(xù)版本中引入的改進(jìn)。 Java教程是希望使用Java編程語言創(chuàng)建應(yīng)用程序的程序員的實用指南,其中包括數(shù)百個完整的工作示例和數(shù)十個課程,相關(guān)課程組被組織成教程。 覆蓋基礎(chǔ)知識的路徑 這些教程以書籍的形式提供,如Java教程,第六版,前往Amazon.com購買。 入門 介紹Java技術(shù)和安裝Java開發(fā)軟件并使用...
閱讀 2318·2021-11-22 13:54
閱讀 3435·2019-08-29 12:25
閱讀 3513·2019-08-28 18:29
閱讀 3660·2019-08-26 13:40
閱讀 3326·2019-08-26 13:32
閱讀 1046·2019-08-26 11:44
閱讀 2297·2019-08-23 17:04
閱讀 3024·2019-08-23 17:02