TL;DR:这个问题已经通过JDK-11解决了;答案的末尾是JDK-11输出的一个例子,用于比较。javac
事实上,每个可抛出的都是一个实例,这在Java字节代码/ JVM的不同位置被暗示。即使任何处理程序都旨在表示可能超出类型层次结构的内容,这个想法也失败了,因为今天的类文件必须具有包含异常处理程序的for方法,并且它将引用任何可抛出的任意可抛出作为1的实例。java.lang.Throwable
Throwable
StackMapTable
StackMapTable
java.lang.Throwable
即使使用旧类型的推断验证程序,重新抛出可抛出的处理程序也隐式包含断言,即任何可抛出对象都是其实例,因为这是唯一允许抛出的对象。java.lang.Throwable
athrow
http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.athrow
objectref 必须是类型,并且必须引用的对象是 类或 子类的实例。reference
Throwable
Throwable
简短的回答:不,不可能有java.lang.Throwable
(或子类)实例以外的其他东西可以被抛出或捕获的情况。
我试图创建一个尝试使用资源语句的最小示例来分析 .结果清楚地表明,该结构是内部工作方式的产物,但不能是故意的。javac
javac
该示例如下所示:
public static void tryWithAuto() throws Exception {
try (AutoCloseable c=dummy()) {
bar();
}
}
private static AutoCloseable dummy() {
return null;
}
private static void bar() {
}
(我编译了jdk1.8.0_20
)
我将异常处理程序表放在生成的字节代码的开头,以便在查看指令序列时更容易引用位置:
Exception table:
from to target type
17 23 26 Class java/lang/Throwable
6 9 44 Class java/lang/Throwable
6 9 49 any
58 64 67 Class java/lang/Throwable
44 50 49 any
现在按照说明:
开头很简单,使用两个局部变量,一个用于保存(索引 0),另一个用于可能的可抛出(索引 1,初始化为 )。 并被调用,然后检查以查看是否必须关闭它。AutoCloseable
null
dummy()
bar()
AutoCloseable
null
0: invokestatic #2 // Method dummy:()Ljava/lang/AutoCloseable;
3: astore_0
4: aconst_null
5: astore_1
6: invokestatic #3 // Method bar:()V
9: aload_0
10: ifnull 86
如果不是,我们到达这里,第一件奇怪的事情发生了,绝对要检查的可投掷物AutoCloseable
null
null
null
13: aload_1
14: ifnull 35
下面的代码将关闭 ,由上表中的第一个异常处理程序保护,该处理程序将调用 。因为在这一点上,变量#1是死代码:AutoCloseable
addSuppressed
null
17: aload_0
18: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
23: goto 86
26: astore_2
27: aload_1
28: aload_2
29: invokevirtual #6 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
32: goto 86
请注意,死代码的最后一个指令是 ,a 的分支,所以如果上面的代码不是死代码,我们可能会开始想知道为什么要在之后被忽略的 a 上调用。goto 86
return
addSuppressed
Throwable
现在跟随在变量 #1 为(始终读取)时执行的代码。它只是调用指令并分支到指令,而不会捕获任何异常,因此 by 引发的异常会传播到调用方:null
close
return
close()
35: aload_0
36: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
41: goto 86
现在我们进入第二个异常处理程序,覆盖语句的主体,声明为 catch ,读取所有异常。它按预期将 存储到变量 #1 中,但也将其存储到过时的变量 #2 中。然后,它会重新抛出 .try
Throwable
Throwable
Throwable
44: astore_2
45: aload_2
46: astore_1
47: aload_2
48: athrow
下面的代码是两个异常处理程序的目标。首先,它是覆盖与处理程序相同范围的多余任何异常处理程序的目标,因此,正如您所怀疑的那样,此处理程序不执行任何操作。此外,它是第四个异常处理程序的目标,捕获任何内容并覆盖上面的异常处理程序,因此我们稍后捕获指令#48右一指令的重新抛出的异常。为了使事情变得更加有趣,异常处理程序比上面的处理程序涵盖更多;以#50结尾,排他性,它甚至涵盖了它自己的第一个指令:Throwable
49: astore_3
因此,第一件事是引入第三个变量来保存相同的可抛出性。现在,已检查 。AutoCloseable
null
50: aload_0
51: ifnull 84
现在检查变量 #1 的可抛出项。只有当假设的可投掷物不存在时,它才可能。但请注意,在这种情况下,整个代码将被验证者拒绝,因为声明所有变量和操作数堆栈条目都保存任何可抛出的可赋值null
null
Throwable
StackMapTable
java.lang.Throwable
54: aload_1
55: ifnull 78
58: aload_0
59: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
64: goto 84
最后但并非最不重要的一点是,我们有一个异常处理程序,当存在挂起的异常时,它处理由 close 引发的异常,这将调用并重新引发主异常。它引入了另一个局部变量,表明即使在适当的情况下也确实从不使用交换
。addSuppressed
javac
67: astore 4
69: aload_1
70: aload 4
72: invokevirtual #6 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
75: goto 84
因此,仅当 catch any 可能暗示其他情况时,才会调用以下两条指令。代码路径在 #84 处与常规大小写联接。java.lang.Throwable
78: aload_0
79: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
84: aload_3
85: athrow
86: return
因此,底线是,任何附加的异常处理程序仅负责四条指令的死代码,#54,#55,#78和#79,而由于其他原因(#17 - #32)还有更多的死代码,再加上一个奇怪的“throw-and-catch”(#44 - #48)代码,这也可能是处理与.此外,一个异常处理程序有一个错误的范围覆盖自身,这可能与注释中建议的“Sun的javac产生的奇怪异常表条目”有关。Throwable
顺便说一句,Eclipse 生成的代码更简单,指令序列仅占用 60 个字节而不是 87 个字节,只有两个预期的异常处理程序和三个局部变量,而不是五个。在更紧凑的代码中,它处理了以下可能的情况:正文引发的异常可能与引发的异常相同,在这种情况下,不得调用。生成的代码不关心这一点。close
addSuppressed
javac
0: aconst_null
1: astore_0
2: aconst_null
3: astore_1
4: invokestatic #18 // Method dummy:()Ljava/lang/AutoCloseable;
7: astore_2
8: invokestatic #22 // Method bar:()V
11: aload_2
12: ifnull 59
15: aload_2
16: invokeinterface #25, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
21: goto 59
24: astore_0
25: aload_2
26: ifnull 35
29: aload_2
30: invokeinterface #25, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
35: aload_0
36: athrow
37: astore_1
38: aload_0
39: ifnonnull 47
42: aload_1
43: astore_0
44: goto 57
47: aload_0
48: aload_1
49: if_acmpeq 57
52: aload_0
53: aload_1
54: invokevirtual #30 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
57: aload_0
58: athrow
59: return
Exception table:
from to target type
8 11 24 any
4 37 37 any
从 JDK-11 开始,将示例编译为javac
Code:
0: invokestatic #2 // Method dummy:()Ljava/lang/AutoCloseable;
3: astore_0
4: invokestatic #3 // Method bar:()V
7: aload_0
8: ifnull 42
11: aload_0
12: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
17: goto 42
20: astore_1
21: aload_0
22: ifnull 40
25: aload_0
26: invokeinterface #4, 1 // InterfaceMethod java/lang/AutoCloseable.close:()V
31: goto 40
34: astore_2
35: aload_1
36: aload_2
37: invokevirtual #6 // Method java/lang/Throwable.addSuppressed:(Ljava/lang/Throwable;)V
40: aload_1
41: athrow
42: return
Exception table:
from to target type
4 7 20 Class java/lang/Throwable
25 31 34 Class java/lang/Throwable
它现在的冗余甚至比ECJ编译的版本还要少。它仍然不检查可抛出项是否相同,但我宁愿添加另一个异常处理程序条目,涵盖调用指令并将重新抛出代码定位在 ,而不是插入此角情况的预检查。然后,它仍然比替代方案的代码少。addSuppressed
40