我在解决一些看起来像这样的代码时遇到了非常痛苦的故障排除经验:
try { doSomeStuff() doMore() } finally { doSomeOtherStuff() }
这个问题很难解决,因为doSomeStuff()引发了异常,这又导致doSomeOtherStuff()也抛出异常.第二个异常(由finally块抛出)被抛到我的代码中,但它没有第一个异常的句柄(从doSomeStuff()抛出),这是问题的真正根本原因.
如果代码说了这个,那么问题就很明显了:
try { doSomeStuff() doMore() } catch (Exception e) { log.error(e); } finally { doSomeOtherStuff() }
所以,我的问题是:
是没有任何catch块使用的finally块是一个众所周知的java反模式?(这显然是一个不太明显的子类,显然是众所周知的反模式"不要狼吞虎咽的例外!")
一般来说,不,这不是反模式.finally块的重点是确保无论是否抛出异常,都会清理内容.异常处理的重点在于,如果你不能处理它,你可以通过相对干净的带外信令异常处理提供给它的人.如果你需要确保在抛出异常时清理了东西,但是无法在当前范围内正确处理异常,那么这是正确的做法.你可能想要更加小心确保你的finally块不会抛出.
我认为真正的"反模式"是在一个finally
可以抛出的块中做某事,而不是没有捕获.
一点也不.
最后的代码是什么错.
请记住,最终将永远执行,并且只是冒险(正如您刚刚目睹的那样)放置可能会在那里引发异常的东西.
尝试最终没有任何问题绝对没有错.考虑以下:
InputStream in = null; try { in = new FileInputStream("file.txt"); // Do something that causes an IOException to be thrown } finally { if (in != null) { try { in.close(); } catch (IOException e) { // Nothing we can do. } } }
如果抛出异常并且此代码不知道如何处理它,那么异常应该将调用堆栈冒泡到调用者.在这种情况下,我们仍然想要清理流,所以我认为有一个没有捕获的try块是完全合理的.
我认为它远非反模式,而且在解决方法执行过程中获取资源的关键时,我经常这样做.
在处理文件句柄(用于写入)时,我做的一件事是在使用IOUtils.closeQuietly方法关闭流之前刷新流,该方法不会抛出异常:
OutputStream os = null;
OutputStreamWriter wos = null;
try {
os = new FileOutputStream(...);
wos = new OutputStreamWriter(os);
// Lots of code
wos.flush();
os.flush();
finally {
IOUtils.closeQuietly(wos);
IOUtils.closeQuietly(os);
}
我喜欢这样做,原因如下:
关闭文件时忽略异常并不完全安全 - 如果还有未写入文件的字节,则文件可能不处于调用者期望的状态;
因此,如果在flush()方法期间引发异常,它将传播给调用者,但我仍然会确保所有文件都已关闭.方法IOUtils.closeQuietly(...)比相应的try ... catch ...忽略我块更简洁;
如果使用多个输出流,则flush()方法的顺序很重要.通过将其他流作为构造函数传递而创建的流应首先刷新.对于close()方法,同样的事情是有效的,但我认为flush()更清楚.