抓住Throwable进行清理是否可以?

举个这样的例子:

public List readThings(List files) throws IOException { ImmutableList.Builder 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 readThings(List files) throws IOException { ImmutableList.Builder 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有什么不同。 在任何一种情况下,exception都会传播。 在任何一种情况下,当可能发生OutOfMemoryError时,正在运行其他代码。

那么终于真的更安全吗?

ThrowableExceptionError的父类型,因此捕获Throwable意味着捕获exception和错误。 Exception是你可以恢复的东西(比如IOException ),一个更严重的错误,通常你不能轻易恢复(比如ClassNotFoundError )所以除非你知道你在做什么,否则捕获错误没有多大意义。

抓住Throwable进行清理是否可以?

总之一句话……不。

问题是如果你捕获并重新抛出Throwable ,你必须声明该方法抛出Throwable ……这将导致调用该方法的任何问题:

  • 调用者现在必须“处理”(可能传播) Throwable
  • 程序员现在不会以编译器错误的forms从编译器获得任何帮助,关于已检查的exception尚未处理。 (当您“处理” Throwable时,将包括尚未处理的所有已检查和未检查的exception。)

一旦你开始沿着这条路走下去, throws Throwable就会像疾病一样通过呼叫层次传播……


关闭资源的正确方法是finally使用,或者如果您正在编写Java 7或更高版本,则使用“尝试使用资源”,并使您的资源可自动关闭。

(在您的示例中,这有点棘手,但您可以扩展现有的List类以创建一个“closeable list”类,其中close()方法关闭所有列表成员。


确实,对于Java 7及更高版本,您可以放弃将封闭方法声明为仅抛出将被捕获的已检查exception。 然而,捕捉Throwable进行清理并不是人们期望看到的。 人们希望看到一个finally的清理条款。 如果你以一种时髦的方式做到这一点,那么你的代码就更难以阅读……而且这不是“好”。 即使你的方式更简洁也不行。

此外,您的版本将无法使用Java 6及更早版本进行编译。


简而言之,我同意您的代码的审核者。

我唯一同意的是,如果你的版本和finally版本都是“安全的”,假设它们是正确实现的。 (问题在于程序员必须在你的情况下更加努力地认识到它是安全的……因为你编写了非惯用的方法。)

这是尝试回答我自己的问题,但它使用实验和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."":(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."":(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."":(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."":(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."":(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."":(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; } } 

如果有人真的写了那个代码,你会嘲笑它们。 在成功分支中,成功始终是正确的,因此有大量代码永远不会运行,因此您生成的字节码永远不会被执行,只会使您的类文件膨胀。 在exception分支中,成功始终为false,因此您在执行清理之前执行不必要的检查,然后再进行清理,这只会增加类文件的大小。

最重要的是要注意:

catch (Throwable)finally解决方案实际上都捕获了所有exception。

因此,就回答这个问题而言,“抓住Throwable进行清理是否可以?”……

我仍然不确定,但我知道如果抓住Throwable并不好,那么finally也不能使用它。 如果finally还不行,还剩下什么?

抓住Throwablefinally是不可互换的。

  • 无论退出原因如何finally子句中的代码都将在退出块时执行。 如果没有抛出exception,它将被执行。 因此,必须始终执行清理代码的适当位置。

  • 只有在抛出exception时才会执行catch Throwable代码。