简答
问:手工写try、catch、finally还是用java7提供的语法糖:try-with-resources?
答:当然是try-with-resources
原因如下:
- 极大简化了代码
- try-with-resources还会使用Suppressed异常的功能,来避免原异常“被消失”
详解
此事说来话长,我们先从finally代码块的编译原理说起。
示例代码如下:
class TryFinally {
public static void main(String[] args) {
try {
System.out.println("xxx");
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("fff");
}
}
}
编译后的机器码部分如下:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: getstatic
3: ldc
5: invokevirtual
8: getstatic
11: ldc
13: invokevirtual
16: goto 46
19: astore_1
20: aload_1
21: invokevirtual
24: getstatic
27: ldc
29: invokevirtual
32: goto 46
35: astore_2
36: getstatic
39: ldc
41: invokevirtual
44: aload_2
45: athrow
46: return
Exception table:
from to target type
0 8 19 Class java/lang/Exception
0 8 35 any
19 24 35 any
上面可以看到finally内的代码在编译器中复制了3份,其中,前两份分别位于try代码块和catch代码块的正常执行路径出口。最后一份则作为异常处理器,监控try代码块以及catch代码块。它将捕获try代码块触发的、未被catch代码块捕获的异常,以及catch代码块触发的异常。
这里有一个小问题,如果catch代码块捕获了异常,并且触发了另一个异常,那么finally捕获并且重抛的异常是哪个呢?答案是后者。也就是说原本的异常便会被忽略掉。
在java7中引入了Suppressed异常来解决这个问题。这个新特性允许开发人员将一个异常附于另一个异常之上。因此,抛出的异常可以附带多个异常的信息。
然而,Java层面的finally代码块缺少指向所捕获异常的引用,所以这个新特性使用起来非常繁琐。
为此,Java 7专门构造了一个名为try-with-resources的语法糖,在字节码层面自动使用Suppressed异常。当然,该语法糖的主要目的并不是使用Suppressed异常,而是精简资源打开关闭的用法。
示例代码:
public class Foo implements AutoCloseable {
private final String name;
public Foo(String name) {
this.name = name;
}
@Override
public void close() {
throw new RuntimeException(name);
}
public static void main(String[] args) {
try (Foo foo0 = new Foo("Foo0")) {
throw new RuntimeException("Initial");
}
}
}
可以看到编译后的class文件,添加了supressed异常和自动关闭流:
public class Foo implements AutoCloseable {
private final String name;
public Foo(String var1) {
this.name = var1;
}
public void close() {
throw new RuntimeException(this.name);
}
public static void main(String[] var0) {
Foo var1 = new Foo("Foo0");
Throwable var2 = null;
try {
throw new RuntimeException("Initial");
} catch (Throwable var10) {
var2 = var10;
throw var10;
} finally {
if (var1 != null) {
if (var2 != null) {
try {
var1.close();
} catch (Throwable var9) {
var2.addSuppressed(var9);
}
} else {
var1.close();
}
}
}
}
}
综上,try-with-resources真香。
扩展小知识
控制流语句与finally代码块之间的协作又会是什么样呢?
示例代码如下:
public class SwitchWithFinally {
private int tryBlock;
private int catchBlock;
private int finallyBlock;
private int methodExit;
public void test() {
for (int i = 0; i < 100; i++) {
try {
tryBlock = 0;
if (i < 50) {
continue;
} else if (i < 80) {
break;
} else {
return;
}
} catch (Exception e) {
catchBlock = 1;
} finally {
finallyBlock = 2;
}
}
methodExit = 3;
}
}
编译后的机器码,部分如下:
public void test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=1
0: iconst_0
1: istore_1
2: iload_1
3: bipush 100
5: if_icmpge 75
8: aload_0
9: iconst_0
10: putfield
13: iload_1
14: bipush 50
16: if_icmpge 27
19: aload_0
20: iconst_2
21: putfield
24: goto 69
27: iload_1
28: bipush 80
30: if_icmpge 41
33: aload_0
34: iconst_2
35: putfield
38: goto 75
41: aload_0
42: iconst_2
43: putfield
46: return
47: astore_2
48: aload_0
49: iconst_1
50: putfield
53: aload_0
54: iconst_2
55: putfield
58: goto 69
61: astore_3
62: aload_0
63: iconst_2
64: putfield
67: aload_3
68: athrow
69: iinc 1, 1
72: goto 2
75: aload_0
76: iconst_3
77: putfield
80: return
Exception table:
from to target type
8 19 47 Class java/lang/Exception
27 33 47 Class java/lang/Exception
8 19 61 any
27 33 61 any
47 53 61 any
如上,共有5份finally代码块,每个分支一份,catch一份,监听整个try-catch一份
|