好的答案已经发布。我只是采取了一种不同的方法,因为这感觉像是一个深入了解一些可能有一天会派上用场的细节的机会,它试图通过阅读一些字节码来回答这个问题。
有几个场景 - 看看
-
try 块中的异常
- 在退出
try 块期间关闭 auto-closeable 时出现异常
- 在处理早期异常期间
closing auto-closeable 资源时出现异常
- 在 try 块中返回,在返回之前执行
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:将字段存储到插槽1的LocalVariableTable(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)、hey hey。
7:调用打印行方法,这会消耗操作数堆栈中的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 加载到操作数堆栈上。检查ps 是否为null,如果为null,则从函数中跳转到18 和return。
14 - 18:加载ps,调用close和return。
上面的内容特别有趣,因为它表明如果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 处处理。对于第 24-28 行的第 31 行也是如此。
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:这与我们之前看到的相同模式 close 仅在 ps 不是 null 时调用
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 时出现异常。
** 引发异常,try 块退出 abruptly
-
closingauto-closeable 资源在处理早期异常时出现异常。
** 将抑制的异常添加到原始异常并抛出原始异常。 try 块退出 abruptly
- 在 try 块中返回,在返回之前关闭执行。
** 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)