Java试用资源
Java try with resources构造(又名Java try-with-resources)是一种异常处理机制,当我们完成处理后,可以自动关闭Java InputStream或者JDBC Connection等资源。为此,我们必须在Java try-with-resources块中打开并使用该资源。当执行离开try-with-resources块时,在try-with-resources块中打开的任何资源都会自动关闭,无论是否从try-with-resources块内部或者尝试关闭时引发任何异常。资源。
该Java try-with-resources教程介绍了Java try-with-resources构造如何工作,如何正确使用它以及如何处理从try-with-resources块内部以及在关闭过程中引发的异常。资源。
尝试资源
要查看Java try-with-resources构造如何工作,让我们看一下Java try-with-resources示例:
private static void printFile() throws IOException { try(FileInputStream input = new FileInputStream("file.txt")) { int data = input.read(); while(data != -1){ System.out.print((char) data); data = input.read(); } } }
这个try-with-resources示例显示了如何在try-with-resources块内打开Java FileInputStream,如何从FileInputStream读取一些数据,以及一旦执行离开try-with-resources块,就会自动关闭FileInputStream (不明确可见)。
注意上面的try-with-resources示例中方法的第一行:
try(FileInputStream input = new FileInputStream("file.txt")) {
这是try-with-resources构造。在" try"关键字之后的括号内声明" FileInputStream"变量。另外,实例化一个FileInputStream并将其分配给该变量。
当try块完成时,FileInputStream将自动关闭。这是可能的,因为FileInputStream实现了Java接口java.lang.AutoCloseable。所有实现此接口的类都可以在try-with-resources构造中使用。
试用资源Java 9增强功能
在Java 9之前,必须在try-with-resources构造的try块的括号内创建要自动关闭的资源。从Java 9开始,这不再是必需的。如果引用资源的变量实际上是最终变量,则只需在try块括号内输入对该变量的引用。这是Java 9 try-with-resources增强功能的示例:
private static void printFile() throws IOException { FileInputStream input = new FileInputStream("file.txt"); try(input) { int data = input.read(); while(data != -1){ System.out.print((char) data); data = input.read(); } } }
注意现在如何声明" input"变量,并在try块外部分配了" FileInputStream"。还要注意,"输入"变量是如何在try块的括号内进行引用的。这样,一旦退出try块,Java仍将正确关闭它。
使用多种资源
我们可以在Java try-with-resources块中使用多个资源,并使它们全部自动关闭。这是在try-with-resources块中使用多个资源的示例:
private static void printFile() throws IOException { try( FileInputStream input = new FileInputStream("file.txt"); BufferedInputStream bufferedInput = new BufferedInputStream(input) ) { int data = bufferedInput.read(); while(data != -1){ System.out.print((char) data); data = bufferedInput.read(); } } }
本示例在try关键字之后的括号内创建两个资源。一个" FileInputStream"和一个" BufferedInputStream"。当执行离开try块时,这两个资源都将自动关闭。
结单
在Java try-with-resources构造中声明的资源将以与创建/在括号内列出的顺序相反的顺序关闭。在上一节的示例中,首先将关闭<BufferedInputStream,然后是FileInputStream
。
自定义自动关闭实施
Java try-with-resources构造不仅可以与Java的内置类一起使用。我们也可以在自己的类中实现java.lang.AutoCloseable
接口,并将其与try-with-resources构造一起使用。
AutoClosable接口只有一个名为close()的方法。界面外观如下:
public interface AutoClosable { public void close() throws Exception; }
任何实现此接口的类都可以与Java try-with-resources构造一起使用。这是一个简单的示例实现:
public class MyAutoClosable implements AutoCloseable { public void doIt() { System.out.println("MyAutoClosable doing it!"); } @Override public void close() throws Exception { System.out.println("MyAutoClosable closed!"); } }
doIt()方法不属于AutoClosable接口的一部分。在那里是因为我们希望能够做的不仅仅是关闭对象。
这是一个如何将MyAutoClosable与try-with-resources构造一起使用的示例:
private static void myAutoClosable() throws Exception { try(MyAutoClosable myAutoClosable = new MyAutoClosable()){ myAutoClosable.doIt(); } }
这是调用方法myAutoClosable()时输出到System.out的输出:
MyAutoClosable doing it! MyAutoClosable closed!
如我们所见,try-with-resources是确保正确关闭在try-catch块中使用的资源的强大方法,无论这些资源是我们自己创建的,还是Java的内置组件。
资源尝试式异常处理
Java try-with-resources块的异常处理语义与标准Java try-catch-finally块的异常处理语义有所不同。在大多数情况下,即使我们没有精确地了解它们之间的区别,更改后的语义也比原始try-catch-finally块的语义对我们更有效。即使这样,最好还是在try-with-resources构造中真正理解异常处理的内容。因此,我将在此处解释try-with-resources构造的异常处理语义。
如果从Java try-with-resources块中引发异常,则try块括号内打开的任何资源仍将自动关闭。引发异常将强制执行离开try块,这将强制自动关闭资源。一旦关闭资源,从try块内部抛出的异常将在调用堆栈中传播。
当我们尝试关闭它们时,某些资源也可能会引发异常。万一尝试关闭资源时引发资源异常,在同一try-with-resources块中打开的任何其他资源仍将关闭。关闭所有资源后,失败的关闭尝试尝试中的异常将在调用堆栈中传播。如果从多个资源关闭尝试中引发了多个异常,则遇到的第一个异常将是在调用堆栈中传播的异常。其余的例外将被取消。
如果从try-with-resources块内部抛出异常,并且在关闭资源时(调用close()
)都抛出异常,则try块内部抛出的异常将在调用堆栈中传播。尝试关闭资源时抛出的异常将被抑制。这与正常try-catch-finally块中发生的情况相反,在正常try-catch-finally块中,遇到的最后一个异常是在调用堆栈中传播的异常。
为了更好地理解Java try-with-resources构造的异常处理语义,让我们看一些示例。对于这些示例,我创建了以下" AutoClosable"实现,可以强制使用和尝试关闭时抛出异常:
public class AutoClosableResource implements AutoCloseable { private String name = null; private boolean throwExceptionOnClose = false; public AutoClosableResource(String name, boolean throwExceptionOnClose) { this.name = name; this.throwExceptionOnClose = throwExceptionOnClose; } public void doOp(boolean throwException) throws Exception { System.out.println("Resource " + this.name + " doing operation"); if(throwException) { throw new Exception("Error when calling doOp() on resource " + this.name); } } @Override public void close() throws Exception { System.out.println("Resource " + this.name + " close() called"); if(this.throwExceptionOnClose){ throw new Exception("Error when trying to close resource " + this.name); } } }
首先,让我们看一个使用单个资源的基本示例:
public static void main(String[] args){ try { tryWithResourcesSingleResource(); } catch (Exception e) { e.printStackTrace(); Throwable[] suppressed = e.getSuppressed(); } } public static void tryWithResourcesSingleResource() throws Exception { try(AutoClosableResource resourceOne = new AutoClosableResource("One", false)) { resourceOne.doOp(false); } }
如果将AutoClosableResource构造的第二个参数更改为true,则在尝试关闭时它将引发异常。在这种情况下,尝试关闭时抛出的异常将被沿调用堆栈传播到main()
方法,在那里try-catch块将捕获该异常。在这种情况下,从e.getSuppessed()返回的Throwable数组将是一个空数组(大小为0)。
如果将resourceOne.doOp()
的参数也更改为true
,则doOp()
方法将引发异常。在这种情况下,此异常会沿调用堆栈传播到main()
方法。尝试关闭资源时抛出的异常将在e.getSuppressed()返回的Throwable数组中可用。
让我们看一个使用两个AutoClosable
资源的示例:
public static void main(String[] args){ try { tryWithResourcesTwoResources(); } catch (Exception e) { e.printStackTrace(); Throwable[] suppressed = e.getSuppressed(); System.out.println("suppressed = " + suppressed); } } public static void tryWithResourcesTwoResources() throws Exception { try(AutoClosableResource resourceOne = new AutoClosableResource("One", true); AutoClosableResource resourceTwo = new AutoClosableResource("Two", true) ){ resourceOne.doOp(true); resourceTwo.doOp(false); } }
如果在使用过程中或者尝试关闭时只有一种资源引发异常,则其行为与仅使用一种资源时的行为相同。但是,在上面的示例中,我强制两种资源都试图在尝试关闭时抛出异常,并且在使用时(调用doOp()
时)都强制抛出了异常。在这种情况下,从try块内部抛出的异常将沿调用堆栈传播。尝试关闭资源时抛出的两个异常在e.getSuppressed()返回的Throwable数组中可用。
请记住,在try块内只能抛出一个异常。引发异常后,将立即退出try块代码,并尝试关闭资源。
渔获量
我们可以将catch块添加到try-with-resources块,就像可以将其添加到标准try块一样。如果从try-with-resources块的try块中抛出异常,则catch块将捕获该异常,就像与标准try构造一起使用时一样。
在进入catch块之前,try-with-resources构造将尝试关闭在try块内打开的资源。如果尝试关闭其中一个资源时抛出异常,则可以从catch块内的异常的getSuppressed()方法获取这些异常。这是添加了catch块的Java try-with-resources块的示例:
try(AutoClosableResource resourceOne = new AutoClosableResource("One", true)) { resourceOne.doOp(true); } catch(Exception e) { Throwable[] suppressed = e.getSuppressed(); throw e; }
在上面的示例中," AutoClosableResource"被配置为在调用" doOp()"和尝试将其关闭(通过" close()")时都引发异常。从doOp()抛出的异常被捕获在catch块中,其getSuppressed()方法返回一个数组,该数组具有尝试关闭资源时抛出的异常。
如果仅在尝试关闭资源时才引发异常,则catch块也将捕获它。该异常的getSuppressed()
方法将返回一个空数组,因为没有异常被抑制。
最终块
也可以将finally块添加到Java try-with-resources块中。它的行为就像标准的finally块一样,这意味着它将在执行任何catch块后退出try-with-resources块之前作为最后一步执行。
如果我们从try-with-resources构造的finally块中抛出异常,则所有先前抛出的异常都将丢失!这是一个从Java try-with-resources构造的finally块中引发异常的示例:
public static void main(String[] args){ try { tryWithResourcesSingleResource(); } catch (Exception e) { e.printStackTrace(); Throwable[] suppressed = e.getSuppressed(); } } public static void tryWithResourcesSingleResource() throws Exception { try(AutoClosableResource resourceOne = new AutoClosableResource("One", true)) { resourceOne.doOp(false); } catch(Exception e) { Throwable[] suppressed = e.getSuppressed(); throw e; } finally { throw new Exception("Hey, an exception from the finally block"); } }
注意,在catch块中引发的异常将被忽略,因为从finally块中引发了新的异常。如果没有捕获块,这也将是正确的。然后,从try块内部抛出的任何异常都将丢失,因为从finally块内部抛出了新的异常。以前的任何异常都不会被抑制,因此,它们在finally块引发的异常中不可用。
手动添加抑制的异常
Throwable类具有名为addSuppressed()的方法,该方法将Throwable对象作为参数。使用addSuppressed()
方法,可以在需要时将抑制的异常添加到另一个异常中。这是一个示例,显示了如何将抑制的异常手动添加到Java异常:
Exception finalException = null; try(AutoClosableResource resourceOne = new AutoClosableResource("One", true)) { resourceOne.doOp(false); } catch(Exception e) { finalException = new Exception("Error..."); finalException.addSuppressed(e); for(Throwable suppressed : e.getSuppressed()){ finalException.addSuppressed(suppressed); } } finally { if(finalException != null){ throw finalException; } }
请注意,必须在try-with-resources构造外部声明" Throwable"引用。否则,catch和finally块将无法访问它。
在大多数情况下,我们不需要手动将抑制的异常添加到异常中,但是现在,我们至少已经了解了如何完成此操作,以防万一遇到需要它的情况。
最后尝试,老派风格的资源管理
Java 7中添加了Java try-with-resources构造,在Java 7之前,管理需要显式关闭的资源有些乏味。我们必须手动处理正确的资源关闭。要正确处理这不是一项容易的任务。要了解原因,请查看以下方法,该方法读取文件并将其打印到System.out
中:
private static void printFile() throws IOException { InputStream input = null; try { input = new FileInputStream("file.txt"); int data = input.read(); while(data != -1){ System.out.print((char) data); data = input.read(); } } finally { if(input != null){ input.close(); } } }
用粗体标记的代码可以其中引发"异常"。如我们所见,这可能发生在try块内的3个地方,以及finally块内的1个地方。
无论是否从try块抛出异常,总是执行finally块。这意味着,无论在try块中发生什么,InputStream都是关闭的。或者,尝试关闭的是。如果关闭失败,InputStream的close()方法也可能引发异常。
想象一下,从try块内部引发了异常。然后执行" finally"块。想象一下,从finally块也抛出了异常。我们认为哪个异常会在调用堆栈中传播?
即使从" try"块抛出的异常可能与传播更相关,从" finally"块抛出的异常仍将在调用堆栈中传播。