举个例子:
public List<CloseableThing> readThings(List<File> files) throws IOException {
ImmutableList.Builder<CloseableThing> things = ImmutableList.builder();
try {
for (File file : files) {
things.add(readThing(file))
}
return things.build();
} catch (Throwable t) {
for (CloseableThing thing : things.build()) {
thing.close();
}
throw t;
}
}
代码审核评论的出现是因为通常有一条规则不会捕获Throwable。执行此类仅故障清理的旧模式是:
public List<CloseableThing> readThings(List<File> files) throws IOException {
ImmutableList.Builder<CloseableThing> things = ImmutableList.builder();
boolean success = false;
try {
for (File file : files) {
things.add(readThing(file))
}
success = true;
return things.build();
} finally {
if (!success) {
for (CloseableThing thing : things.build()) {
thing.close();
}
}
}
}
我发现这有点乱,并不完全明白它是否与捕捉Throwable有什么不同。在任何一种情况下,异常都会传播。在任何一种情况下,当可能发生OutOfMemoryError时,都会运行其他代码。
最终真的更安全吗?
答案 0 :(得分:8)
Throwable 是例外和错误的父类型,因此捕获 Throwable 意味着同时捕获两个例外作为错误。异常是你可以恢复的(比如 IOException ),错误是更严重的,通常你不能轻易恢复(比如 ClassNotFoundError )所以它不会除非你知道自己在做什么,否则很难理解错误。
答案 1 :(得分:7)
抓住Throwable进行清理是否可以?
一句话......不。
问题是如果你抓住并重新抛出Throwable
,你必须声明该方法抛出Throwable
...这将导致任何问题调用方法:
Throwable
。Throwable
时,将包括尚未处理的所有已检查和未检查的例外。)一旦你开始沿着这条路走下去,throws Throwable
就会像疾病一样通过呼叫层次传播......
关闭资源的正确方法是使用finally
,或者如果您要编写Java 7或更高版本,请使用“尝试使用资源”,并使资源自动关闭。
(在您的示例中,这有点棘手,但您可以扩展现有的List
类以创建“可关闭列表”类,其中close()
方法关闭所有列表成员。< / p>
对于Java 7及更高版本,您可以逃避声明封闭方法仅抛出将被捕获的已检查异常。然而,捕捉Throwable进行清理并不是人们期望看到的。人们期望看到finally
条款进行清理。如果你以一种时髦的方式做到这一点,你就会让你的代码更难阅读...而且这不是“好”。即使你的方式更简洁也不行。
此外,您的版本将不会使用Java 6及更早版本进行编译。
简而言之,我同意您的代码审核人。
我唯一同意的是,如果您的版本和finally
版本都是“安全的”,假设它们已正确实施。 (问题是程序员必须在你的情况下更加努力地认识到它是安全的......因为你编写了非惯用的方法。)
答案 2 :(得分:1)
这是尝试回答我自己的问题,但是它使用了实验和Java编译器产生的结果,所以它并没有特别针对哲学或类似的东西。
以下是catch-cleanup-and-rethrow的一些示例代码:
public CompoundResource catchThrowable() throws Exception {
InputStream stream1 = null;
InputStream stream2 = null;
try {
stream1 = new FileInputStream("1");
stream2 = new FileInputStream("2");
return new CompoundResource(stream1, stream2);
} catch (Throwable t) {
if (stream2 != null) {
stream2.close();
}
if (stream1 != null) {
stream1.close();
}
throw t;
}
}
编译为以下字节码:
public Exceptions$CompoundResource catchThrowable() throws java.lang.Exception;
Code:
0: aconst_null
1: astore_1
2: aconst_null
3: astore_2
4: new #2 // class java/io/FileInputStream
7: dup
8: ldc #3 // String 1
10: invokespecial #4 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
13: astore_1
14: new #2 // class java/io/FileInputStream
17: dup
18: ldc #5 // String 2
20: invokespecial #4 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
23: astore_2
24: new #6 // class Exceptions$CompoundResource
27: dup
28: aload_0
29: aload_1
30: aload_2
31: invokespecial #7 // Method Exceptions$CompoundResource."<init>":(LExceptions;Ljava/io/Closeable;Ljava/io/Closeable;)V
34: areturn
35: astore_3
36: aload_2
37: ifnull 44
40: aload_2
41: invokevirtual #9 // Method java/io/InputStream.close:()V
44: aload_1
45: ifnull 52
48: aload_1
49: invokevirtual #9 // Method java/io/InputStream.close:()V
52: aload_3
53: athrow
Exception table:
from to target type
4 34 35 Class java/lang/Throwable
接下来是一些代码,用于check-for-failure-in-finally-and-cleanup,否则使用相同的语义:
public CompoundResource finallyHack() throws Exception {
InputStream stream1 = null;
InputStream stream2 = null;
boolean success = false;
try {
stream1 = new FileInputStream("1");
stream2 = new FileInputStream("2");
success = true;
return new CompoundResource(stream1, stream2);
} finally {
if (!success) {
if (stream2 != null) {
stream2.close();
}
if (stream1 != null) {
stream1.close();
}
}
}
}
编译如下:
public Exceptions$CompoundResource finallyHack() throws java.lang.Exception;
Code:
0: aconst_null
1: astore_1
2: aconst_null
3: astore_2
4: iconst_0
5: istore_3
6: new #2 // class java/io/FileInputStream
9: dup
10: ldc #3 // String 1
12: invokespecial #4 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
15: astore_1
16: new #2 // class java/io/FileInputStream
19: dup
20: ldc #5 // String 2
22: invokespecial #4 // Method java/io/FileInputStream."<init>":(Ljava/lang/String;)V
25: astore_2
26: iconst_1
27: istore_3
28: new #6 // class Exceptions$CompoundResource
31: dup
32: aload_0
33: aload_1
34: aload_2
35: invokespecial #7 // Method Exceptions$CompoundResource."<init>":(LExceptions;Ljava/io/Closeable;Ljava/io/Closeable;)V
38: astore 4
40: iload_3
41: ifne 60
44: aload_2
45: ifnull 52
48: aload_2
49: invokevirtual #9 // Method java/io/InputStream.close:()V
52: aload_1
53: ifnull 60
56: aload_1
57: invokevirtual #9 // Method java/io/InputStream.close:()V
60: aload 4
62: areturn
63: astore 5
65: iload_3
66: ifne 85
69: aload_2
70: ifnull 77
73: aload_2
74: invokevirtual #9 // Method java/io/InputStream.close:()V
77: aload_1
78: ifnull 85
81: aload_1
82: invokevirtual #9 // Method java/io/InputStream.close:()V
85: aload 5
87: athrow
Exception table:
from to target type
6 40 63 any
63 65 63 any
仔细观察这里发生了什么,它似乎生成了相同的字节码,就好像你在返回点和catch块内复制了整个finally块一样。换句话说,就好像你写了这样:
public CompoundResource finallyHack() throws Exception {
InputStream stream1 = null;
InputStream stream2 = null;
boolean success = false;
try {
stream1 = new FileInputStream("1");
stream2 = new FileInputStream("2");
success = true;
CompoundResource result = new CompoundResource(stream1, stream2);
if (!success) {
if (stream2 != null) {
stream2.close();
}
if (stream1 != null) {
stream1.close();
}
}
return result;
} catch (any t) { // just invented this syntax, this won't compile
if (!success) {
if (stream2 != null) {
stream2.close();
}
if (stream1 != null) {
stream1.close();
}
}
throw t;
}
}
如果有人真的写了那段代码,你会嘲笑它们。在成功分支中,成功始终是正确的,因此有大量代码永远不会运行,因此您将生成永远不会执行的字节码,只会使您的类文件膨胀。在异常分支中,成功始终为false,因此在执行清理之前,您必须对该值执行不必要的检查,这必须再次增加,这只会增加类文件的大小。
最重要的是要注意:
catch (Throwable)
和finally
解决方案实际上都会捕获所有例外情况。
就回答这个问题而言,&#34;抓住Throwable
进行清理是否可行?&#34; ...
我仍然不确定,但我知道如果抓不到Throwable
就行不通,那么使用finally
也不行。如果finally
不正常,还剩下什么?
答案 3 :(得分:0)
抓取Throwable
和finally
不可互换。
{<1}}子句中的代码将在退出块时执行,无论是否退出。如果没有抛出异常,它将被执行。因此,必须始终执行清理代码的适当位置。
如果抛出异常,finally
catch
代码将仅执行。