捕获和处理异常
本节描述如何使用三个异常处理程序组件 — try、catch和finally块 — 来编写异常处理程序,然后,解释了Java SE 7中引入的try-with-resources语句,try-with-resources语句特别适用于使用Closeable资源的情况,例如流。
本节的最后一部分将介绍一个示例,并分析各种场景中发生的情况。
以下示例定义并实现名为ListOfNumbers的类,构造时,ListOfNumbers创建一个ArrayList,其中包含10个具有顺序值0到9的整数元素,ListOfNumbers类还定义了一个名为writeList的方法,该方法将数字列表写入名为OutFile.txt的文本文件中,此示例使用java.io中定义的输出类,这些类在基础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构造函数初始化文件上的输出流,如果无法打开该文件,则构造函数将抛出IOException。对ArrayList类的get方法的调用,如果其参数的值太小(小于0)或太大(大于ArrayList当前包含的元素数),则抛出IndexOutOfBoundsException。
如果你尝试编译ListOfNumbers类,编译器将输出有关FileWriter构造函数抛出的异常的错误消息,但是,它不会显示有关get抛出的异常的错误消息,原因是构造函数抛出的异常IOException是一个经过检查的异常,而get方法抛出的异常(IndexOutOfBoundsException)是一个未经检查的异常。
现在你已熟悉ListOfNumbers类以及可以在其中抛出异常的位置,你已准备好编写异常处理程序来捕获和处理这些异常。
try块构造异常处理程序的第一步是使用try块将可能引发异常的代码括起来,通常,try块如下所示:
try { code } catch and finally blocks . . .
标记为code的示例段中包含一个或多个可能引发异常的合法代码行(catch和finally块将在接下来的两个小节中解释)。
要从ListOfNumbers类构造writeList方法的异常处理程序,请将writeList方法的异常抛出语句包含在try块中,有不止一种方法可以做到这一点,你可以将可能引发异常的每行代码放在其自己的try块中,并为每个代码提供多带带的异常处理程序。或者,你可以将所有writeList代码放在一个try块中,并将多个处理程序与它相关联,以下列表对整个方法使用一个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块中发生异常,则该异常由与之关联的异常处理程序处理,要将异常处理程序与try块关联,必须在其后面放置一个catch块。
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块中为其分配任何值。finally块
当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-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接口的类请参阅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语句正常退出。
场景1:发生异常创建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"); /****** } } }场景2:try块正常退出
在这种情况下,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"); } } }上一篇:捕获或指定要求 下一篇:如何抛出异常
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/77482.html
捕获或指定要求 有效的Java编程语言代码必须遵守捕获或指定需求,这意味着可能抛出某些异常的代码必须包含以下任一项: 捕获异常的try语句,try必须为异常提供处理程序,如捕获和处理异常中所述。 一种方法,指定它可以抛出异常,该方法必须提供一个throws子句,列出异常,如通过方法抛出指定异常中所述。 不符合捕获或指定要求的代码将无法编译。 并非所有异常都受捕获或指定要求的约束,为了理解原因,...
异常的优点 现在你已经知道了什么是异常以及如何使用它们,现在是时候了解在程序中使用异常的优势了。 优点1:将错误处理代码与常规代码分开 异常提供了从程序的主逻辑中分离异常发生时应该做什么的细节的方法,在传统的编程中,错误检测、报告和处理通常会导致混乱的意大利面代码,例如,考虑这里的伪代码方法将整个文件读入内存。 readFile { open the file; determine...
如何抛出异常 在捕获异常之前,某些代码必须抛出一个,任何代码都可能抛出异常:你的代码,来自其他人编写的包中的代码,例如Java平台附带的包或Java运行时环境,无论抛出什么异常,它总是使用throw语句抛出。 你可能已经注意到,Java平台提供了许多异常类,所有类都是Throwable类的后代,并且所有类都允许程序区分在程序执行期间可能发生的各种类型的异常。 你还可以创建自己的异常类来表示你编写的...
什么是异常? exception一词是exceptional event这一短语的简写。 定义:异常是在程序执行期间发生的事件,它会破坏程序指令的正常流程。 当方法中发生错误时,该方法会创建一个对象并将其交给运行时系统,该对象称为异常对象,包含有关错误的信息,包括错误发生时的类型和程序状态,创建异常对象并将其交给运行时系统称为抛出异常。 在方法抛出异常后,运行时系统会尝试查找处理它的内容,处理异常...
Java™ 教程 Java教程是为JDK 8编写的,本页面中描述的示例和实践没有利用在后续版本中引入的改进。 Java教程是希望使用Java编程语言创建应用程序的程序员的实用指南,其中包括数百个完整的工作示例和数十个课程,相关课程组被组织成教程。 覆盖基础知识的路径 这些教程以书籍的形式提供,如Java教程,第六版,前往Amazon.com购买。 入门 介绍Java技术和安装Java开发软件并使用...
阅读 2167·2021-11-22 13:54
阅读 3359·2019-08-29 12:25
阅读 3408·2019-08-28 18:29
阅读 3528·2019-08-26 13:40
阅读 3254·2019-08-26 13:32
阅读 933·2019-08-26 11:44
阅读 2195·2019-08-23 17:04
阅读 2949·2019-08-23 17:02