繁体   English   中英

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

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

我想知道在try-with-resources块中放置return语句是否会阻止资源自动关闭。

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

如果我写这样的东西,连接会被关闭吗? 在 Oracle 文档中指出:

try-with-resources 语句确保每个资源在语句结束时关闭。

如果由于 return 语句而从未到达语句的末尾会发生什么?

根据Oracle 的教程,“无论 try 语句是正常完成还是突然完成,[资源] 都将关闭”。 abruptly定义为来自异常。

返回try是一个突然完成的例子,如JLS 14.1所定义。

资源将自动关闭(即使使用return语句),因为它实现了AutoCloseable接口。 这是一个输出“成功关闭”的示例:

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");
    }
}

AutoCloseable接口可以使代码的执行顺序乍一看令人困惑。 让我们用一个例子来解释一下:

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());
    }
}

上面的代码尝试打开一些Resource并使用该资源进行一些业务逻辑(在这种情况下只是一些算术)。 运行代码将打印:

open
close
4

因此Resource在退出 try-with-resource 块之前关闭。 为了弄清楚到底发生了什么,让我们重新组织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;
    }

从概念上讲,这就是将return放置在 try-with-resource 块中时发生的事情。 return操作移到 try-with-resource 块之后,以允许AutoCloseable对象在返回之前关闭。

因此我们可以得出结论,try-with-resource 块中的return操作只是语法糖,您不必担心在AutoCloseable关闭之前返回。

好的答案已经发布。 我只是采取了不同的方法,因为这感觉像是一个深入研究一些可能有一天会派上用场的细节的机会,它试图通过阅读一些字节码来回答这个问题。

有几个场景 - 看看

  • try块中的异常
  • try块上退出期间关闭auto-closeable时的异常
  • 异常,当closingauto-closeable处理前面的异常期间资源
  • 在 try 块中 return,在 return 之前close执行。

第一种情况通常是在 java 中使用try-with首要考虑。 我们可以尝试通过查看字节码来理解其他三种场景。 最后一个场景解决了您的问题。

分解下面main方法的字节码

import java.io.*;

class TryWith {

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

让我们分小部分回顾一下(省略了一些细节)

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

0:获取静态字段System.out
3:将字段存入LocalVariableTable (lvt)中的slot 1。

查看 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:加载psaload_1
5:从常量池加载常量( ldc ), hey hey
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:将ps加载到操作数堆栈中。 检查ps是否为null ,如果为null ,则跳转到18并从函数return
14 - 18:加载ps ,调用closereturn

上述内容特别有趣,因为它表明如果Auto-Closeable资源为null并且不throw异常, try-with块将起作用。 当然,即使它确实有效,也没有实际意义——除非在try块中没有访问资源。 任何访问都会导致 NPE。

以上也是正常流程,万一出现异常怎么办? 让我们来看看异常表

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

这告诉我们,字节码 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:将异常存储到局部变量2
20 - 25:这与我们之前看到的模式相同,仅当ps不为null才会调用close 28:跳转指令到37

        37: aload_2
        38: athrow

37:在位置2加载存储在局部变量表中的对象,之前我们将异常存储在这个位置。
38:抛出异常

但是怎么样过程中发生异常的情况下close ,当close已于帐户前面的异常的执行。 让我们回顾一下异常表

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

那是异常表的第二行,让我们看一下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:次要异常存储在插槽 3 的局部变量中。
32:从插槽 3 重新加载原始异常。
33-34:将次要异常作为抑制异常添加到原始异常中。
37-38: 抛出新的异常,我们之前介绍过这些行。

重新审视我们在开头列出的考虑因素

  • try块上退出期间关闭auto-closeable时出现异常。
    ** 引发异常并且tryabruptly退出
  • 当异常closingauto-closeable处理前面的异常时的资源。
    ** 一个被抑制的异常被添加到原始异常并抛出原始异常。 tryabruptly退出
  • 在 try 块中 return,在 return 之前 close 执行。
    ** close 在 try 块中的return之前执行

重新审视我们在字节码中遇到的auto-closeable资源为null的有趣场景,我们可以用

import java.io.*;

class TryWithAnother {

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

毫不奇怪,我们在控制台上得到输出Hey Hey ,也不例外。

最后但非常重要的是要记住,这个字节码是 JLS 的兼容实现。 这种方法对于确定您的实际执行需要什么非常方便,可能还有其他兼容的替代方案 - 在这种情况下我想不出任何。 但是,考虑到这一点,如果不指定我的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