我想知道是否将return语句放在try-with-resources块中会阻止资源自动关闭。
try(Connection conn = ...) {
return conn.createStatement().execute("...");
}
如果我写这样的东西,连接会被关闭吗?在 Oracle 文档中指出:
try-with-resources 语句确保每个资源在语句结束时关闭。
如果由于 return 语句而从未到达语句的末尾会发生什么?
我想知道是否将return语句放在try-with-resources块中会阻止资源自动关闭。
try(Connection conn = ...) {
return conn.createStatement().execute("...");
}
如果我写这样的东西,连接会被关闭吗?在 Oracle 文档中指出:
try-with-resources 语句确保每个资源在语句结束时关闭。
如果由于 return 语句而从未到达语句的末尾会发生什么?
资源将自动关闭(即使使用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
对象在返回之前关闭。
因此我们可以得出结论,return
try-with-resource 块内的操作只是语法糖,您不必担心在 anAutoCloseable
关闭之前返回。
好的答案已经发布。我只是采取了一种不同的方法,因为这感觉像是一个机会来深入研究一些有一天可能会派上用场的细节,它试图通过阅读一些字节码来回答这个问题。
有几种情况 - 看看
try
块中的异常auto-closeable
上退出期间关闭时出现异常try
closing
的异常auto-closeable
close
return 之前执行。第一个场景通常是try-with
在 java 中使用的首要考虑因素。我们可以通过查看字节码来尝试理解其他三种情况。最后一个场景解决了您的问题。
分解以下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
插槽 1 的 (lvt) 中。
查看 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:加载ps
(aload_1
)
5:从常量池中加载常量( ldc
) 。
7:调用 print line 方法,这会从操作数堆栈中消耗和。hey hey
ps
hey hey
10: aload_1
11: ifnull 18
14: aload_1
15: invokevirtual #5 // Method java/io/PrintStream.close:()V
18: return
10 - 11:加载ps
到操作数堆栈。检查 if ps
isnull
和 if it is null
,跳转到函数18
和return
从函数中跳转。
14 - 18:加载ps
、调用close
和return
。
上面的内容特别有趣,因为它表明如果资源不是异常,则该try-with
块将起作用。当然,即使它确实有效,它也没有实际意义——除非在块中没有访问资源。任何访问都会导致 NPE。Auto-Closeable
null
throw
try
以上也是正常的流程,万一出现异常怎么办?让我们看一下异常表
Exception table:
from to target type
4 10 19 Class java/lang/Throwable
24 28 31 Class java/lang/Throwable
这告诉我们,java.lang.Throwable
字节码 4-10 之间的任何类型异常都在目标 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:这与我们之前看到的模式相同,只有在不是28close
时才会调用
:跳转指令ps
null
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:抛出新异常,我们之前已经介绍过这些行。
重新审视我们在开头列出的考虑因素
auto-closeable
在退出try
块时关闭异常。try
退出块abruptly
closing
。
** 将抑制的异常添加到原始异常并抛出原始异常。块退出auto-closeable
try
abruptly
return
在 try 块之前执行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)