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

时间:2013-06-27 03:10:47

标签: java try-catch finally throwable

举个例子:

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时,都会运行其他代码。

最终真的更安全吗?

4 个答案:

答案 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)

抓取Throwablefinally不可互换。

  • {<1}}子句中的代码将在退出块时执行,无论是否退出。如果没有抛出异常,它将被执行。因此,必须始终执行清理代码的适当位置。

  • 如果抛出异常,finally catch代码将仅执行