Java试用资源

时间:2020-01-09 10:35:58  来源:igfitidea点击:

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"块抛出的异常仍将在调用堆栈中传播。