本节描述如何使用三个异常处理程序组件 — 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"); } } }