4

举一个这样的例子:

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 时,正在运行附加代码。

那么finally真的更安全吗?

4

4 回答 4

8

ThrowableExceptionError的父类型,因此捕获Throwable意味着捕获异常和错误。异常是您可以恢复的东西(例如IOException),错误是更严重的东西,通常您无法轻松恢复(例如ClassNotFoundError ),因此除非您知道自己在做什么,否则捕获错误没有多大意义.

于 2013-06-27T03:20:21.603 回答
7

可以捕获 Throwable 进行清理吗?

一言以蔽之……不。

问题是,如果你 catch 和 rethrow Throwable,你必须声明该方法 throws Throwable...这将对任何调用该方法的东西造成问题:

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

一旦你开始走这条路,throws Throwable就会像疾病一样通过调用层次结构传播......


关闭资源的正确方法是使用finally,或者如果您正在为 Java 7 或更高版本编写代码,请使用“try with resources”,并使您的资源可自动关闭。

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


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

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


简而言之,我同意您的代码审阅者的观点。

我唯一同意的是,如果您的版本和finally版本都是“安全的”,前提是它们已正确实施。(问题是程序员必须在你的情况下更加努力地意识到它是安全的......因为你编码它的非惯用方式。)

于 2013-06-27T03:34:39.287 回答
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

接下来是一些检查失败的代码,在最后和清理中具有相同的语义:

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;
    }
}

如果有人真的写了那个代码,你会嘲笑他们。在成功分支中,成功总是正确的,所以有一大块代码永远不会运行,所以你生成的字节码永远不会被执行,只会让你的类文件膨胀。在异常分支中,成功总是错误的,因此您在进行您知道必须发生的清理之前对值执行不必要的检查,这再次增加了类文件的大小。

最需要注意的是:

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

因此,就回答这个问题而言,“可以捕获Throwable执行清理吗?”...

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

于 2014-04-07T12:24:11.590 回答
0

捕捉Throwablefinally不可互换。

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

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

于 2013-06-27T07:30:42.813 回答