简体   繁体   English

Java 中的 Try-with-resources 和 return 语句

[英]Try-with-resources and return statements in java

I'm wondering if putting a return statement inside a try-with-resources block prevents the resource to be automatically closed.我想知道在try-with-resources块中放置return语句是否会阻止资源自动关闭。

try(Connection conn = ...) {
    return conn.createStatement().execute("...");
}

If I write something like this will the Connection be closed?如果我写这样的东西,连接会被关闭吗? In the Oracle documentation it is stated that:在 Oracle 文档中指出:

The try-with-resources statement ensures that each resource is closed at the end of the statement. try-with-resources 语句确保每个资源在语句结束时关闭。

What happens if the end of the statement is never reached because of a return statement?如果由于 return 语句而从未到达语句的末尾会发生什么?

Based on Oracle's tutorial , "[the resource] will be closed regardless of whether the try statement completes normally or abruptly".根据Oracle 的教程,“无论 try 语句是正常完成还是突然完成,[资源] 都将关闭”。 It defines abruptly as from an exception.abruptly定义为来自异常。

Returning inside the try is an example of abrupt completion, as defined by JLS 14.1 .返回try是一个突然完成的例子,如JLS 14.1所定义。

The resource will be closed automatically (even with a return statement) since it implements the AutoCloseable interface.资源将自动关闭(即使使用return语句),因为它实现了AutoCloseable接口。 Here is an example which outputs "closed successfully":这是一个输出“成功关闭”的示例:

public class Main {

    public static void main(String[] args) {
        try (Foobar foobar = new Foobar()) {
            return;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class Foobar implements AutoCloseable {

    @Override
    public void close() throws Exception {
        System.out.println("closed successfully");
    }
}

The AutoCloseable interface can make the execution order of code confusing at first glance. AutoCloseable接口可以使代码的执行顺序乍一看令人困惑。 Lets run through this with an example:让我们用一个例子来解释一下:

public class Main {

    // An expensive resource which requires opening / closing
    private static class Resource implements AutoCloseable {

        public Resource() {
            System.out.println("open");
        }
        
        @Override public void close() throws Exception {
            System.out.println("close");
        }
    }
    
    // find me a number!
    private static int findNumber() {
        // open the resource
        try(Resource resource = new Resource()) {
            // do some business logic (usually involving the resource) and return answer
            return 2 + 2;
        } catch(Exception e) {
            // resource encountered a problem
            throw new IllegalStateException(e);
        }
    }
    
    public static void main(String[] args) {
        System.out.println(findNumber());
    }
}

The above code attempts to open some Resource and conduct some business logic using the resource (just some arithmetic in this case).上面的代码尝试打开一些Resource并使用该资源进行一些业务逻辑(在这种情况下只是一些算术)。 Running the code will print:运行代码将打印:

open
close
4

Therefore the Resource is closed before exiting the try-with-resource block.因此Resource在退出 try-with-resource 块之前关闭。 To make it clear what exactly is going on, lets reorganise the findNumber() method.为了弄清楚到底发生了什么,让我们重新组织findNumber()方法。

    private static int findNumber() {
        // open the resource
        int number;
        try(Resource resource = new Resource()) {
            // do some business logic and return answer
            number = 2 + 2;
        } catch(Exception e) {
            // resource encountered a problem
            throw new IllegalStateException(e);
        }
        return number;
    }

Conceptually, this is what happens under the hood when return is placed inside a try-with-resource block.从概念上讲,这就是将return放置在 try-with-resource 块中时发生的事情。 The return operation is moved to after the try-with-resource block to allow the AutoCloseable object to close before returning. return操作移到 try-with-resource 块之后,以允许AutoCloseable对象在返回之前关闭。

Therefore we can conclude that a return operation inside a try-with-resource block is just syntactic sugar and you need not worry about returning before an AutoCloseable has closed.因此我们可以得出结论,try-with-resource 块中的return操作只是语法糖,您不必担心在AutoCloseable关闭之前返回。

Good answers have already been posted.好的答案已经发布。 I'm just taking a different approach as it feels like an opportunity to dive into some details that may some day be handy, which is trying to answer the question by reading some bytecode.我只是采取了不同的方法,因为这感觉像是一个深入研究一些可能有一天会派上用场的细节的机会,它试图通过阅读一些字节码来回答这个问题。

There are a few scenarios - to look at有几个场景 - 看看

  • exception in the try block try块中的异常
  • exception when closing the auto-closeable during exiting on the try blocktry块上退出期间关闭auto-closeable时的异常
  • exception when closing the auto-closeable resource during handling an earlier exception异常,当closingauto-closeable处理前面的异常期间资源
  • return in the try block, is close executed prior to return.在 try 块中 return,在 return 之前close执行。

The first scenario is usually top of mind with using try-with in java.第一种情况通常是在 java 中使用try-with首要考虑。 We can try understanding the other three scenarios by looking at the byte code.我们可以尝试通过查看字节码来理解其他三种场景。 The last scenario addresses your question.最后一个场景解决了您的问题。

Breaking down the byte code for the main method below分解下面main方法的字节码

import java.io.*;

class TryWith {

  public static void main(String[] args) {
    try(PrintStream ps = System.out) {
       ps.println("Hey Hey");
       return;
    }
  }
}

Lets review it in small parts (some details elided)让我们分小部分回顾一下(省略了一些细节)

    Code:
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: astore_1

0: get the static field System.out . 0:获取静态字段System.out
3: store the field into the LocalVariableTable (lvt) at slot 1. 3:将字段存入LocalVariableTable (lvt)中的slot 1。

Reviewing the lvt we can confirm that the first slot is of the java.io.PrintStream and it has the name ps查看 lvt 我们可以确认第一个插槽是java.io.PrintStream并且它的名称为ps

      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            4      35     1    ps   Ljava/io/PrintStream;
            0      39     0  args   [Ljava/lang/String;
         4: aload_1
         5: ldc           #3                  // String Hey Hey
         7: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V

4: Load ps ( aload_1 ) 4:加载psaload_1
5: Load the constant ( ldc ), hey hey from the constant pool. 5:从常量池加载常量( ldc ), hey hey
7: Invoke the print line method, this consumes ps and hey hey from the operand stack. 7:调用print line方法,这会从操作数栈中消耗pshey hey

        10: aload_1
        11: ifnull        18
        14: aload_1
        15: invokevirtual #5                  // Method java/io/PrintStream.close:()V
        18: return

10 - 11: load ps onto the operand stack. 10 - 11:将ps加载到操作数堆栈中。 check if ps is null and if it is null , jump to 18 and return from the function.检查ps是否为null ,如果为null ,则跳转到18并从函数return
14 - 18: load ps , invoke close and return . 14 - 18:加载ps ,调用closereturn

The above is of particularly interest because it suggest that try-with block would work if the Auto-Closeable resource is null and not throw an exception.上述内容特别有趣,因为它表明如果Auto-Closeable资源为null并且不throw异常, try-with块将起作用。 Of course even if it did work, it would be moot - unless the resource wasn't accessed in the try block.当然,即使它确实有效,也没有实际意义——除非在try块中没有访问资源。 Any access would result in a NPE.任何访问都会导致 NPE。

The above is also the normal flow, what happens in the even of an exception?以上也是正常流程,万一出现异常怎么办? Lets take a look at the exception table让我们来看看异常表

      Exception table:
         from    to  target type
             4    10    19   Class java/lang/Throwable
            24    28    31   Class java/lang/Throwable

This tells us that any exception of type java.lang.Throwable between byte code 4-10 is handled at target 19. Similarly for lines 24-28 at line 31.这告诉我们,字节码 4-10 之间的任何java.lang.Throwable类型的异常都在目标 19 处处理。对于第 31 行的第 24-28 行也是如此。

        19: astore_2
        20: aload_1
        21: ifnull        37
        24: aload_1
        25: invokevirtual #5                  // Method java/io/PrintStream.close:()V
        28: goto          37

19: Store the exception into local variable 2 . 19:将异常存储到局部变量2
20 - 25: This is the same pattern we saw earlier close is only invoked if ps is not null 28: a jump instruction to 37 20 - 25:这与我们之前看到的模式相同,仅当ps不为null才会调用close 28:跳转指令到37

        37: aload_2
        38: athrow

37: load the object stored in the local variable table at position 2, earlier we stored the exception in this position. 37:在位置2加载存储在局部变量表中的对象,之前我们将异常存储在这个位置。
38: throw the exception 38:抛出异常

However what about the case of an exception occurring during close when the close was executing on account of an earlier exception.但是怎么样过程中发生异常的情况下close ,当close已于帐户前面的异常的执行。 Lets recap the exception table让我们回顾一下异常表

      Exception table:
         from    to  target type
             4    10    19   Class java/lang/Throwable
            24    28    31   Class java/lang/Throwable

That is the second line the exception table, lets look at the corresponding byte code at target 31那是异常表的第二行,让我们看一下target 31处对应的字节码

        31: astore_3
        32: aload_2
        33: aload_3
        34: invokevirtual #7                  // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
        37: aload_2
        38: athrow

31: The secondary exception is stored in the local variable at slot 3. 31:次要异常存储在插槽 3 的局部变量中。
32: Reload the original exception from slot 3. 32:从插槽 3 重新加载原始异常。
33-34: add the secondary exception as the suppressed exception to the original exception. 33-34:将次要异常作为抑制异常添加到原始异常中。
37-38: throw the new exception, we covered these lines earlier. 37-38: 抛出新的异常,我们之前介绍过这些行。

Revisiting our consideration listed at the beginning重新审视我们在开头列出的考虑因素

  • exception when closing the auto-closeable during exiting on the try block.try块上退出期间关闭auto-closeable时出现异常。
    ** an exception is raised and the try block exits abruptly ** 引发异常并且tryabruptly退出
  • exception when closing the auto-closeable resource during handling an earlier exception.当异常closingauto-closeable处理前面的异常时的资源。
    ** A suppressed exception is added to the orignal exception and the original exception is thrown. ** 一个被抑制的异常被添加到原始异常并抛出原始异常。 the try block exits abruptly tryabruptly退出
  • return in the try block, is close executed prior to return.在 try 块中 return,在 return 之前 close 执行。
    ** close is executed prior to the return in the try block ** close 在 try 块中的return之前执行

Revisiting the interesting scenarios of auto-closeable resource being null that we encountered in the byte code, we can test that with重新审视我们在字节码中遇到的auto-closeable资源为null的有趣场景,我们可以用

import java.io.*;

class TryWithAnother {

  public static void main(String[] args) {
    try(PrintStream ps = null) {
       System.out.println("Hey Hey");
       return;
    }
  }
}

Not surprisingly we get the output Hey Hey on the console and no exception.毫不奇怪,我们在控制台上得到输出Hey Hey ,也不例外。

Last but pretty important to keep in mind is that this bytecode is a compliant implementation of the JLS.最后但非常重要的是要记住,这个字节码是 JLS 的兼容实现。 This approach is pretty handy to determine what your actual execution entails, there might be other compliant alternatives - in this situation I can't think of any.这种方法对于确定您的实际执行需要什么非常方便,可能还有其他兼容的替代方案 - 在这种情况下我想不出任何。 However with this in mind this response won't be complete without specifying my javac version但是,考虑到这一点,如果不指定我的javac版本,此响应将不完整

openjdk 11.0.9.1 2020-11-04
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.9.1+1)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.9.1+1, mixed mode)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM