Java尝试/捕获/最终的最佳实践,同时获取/closures资源
在做一个学校项目时,我写了下面的代码:
FileOutputStream fos; ObjectOutputStream oos; try { fos = new FileOutputStream(file); oos = new ObjectOutputStream(fos); oos.writeObject(shapes); } catch (FileNotFoundException ex) { // complain to user } catch (IOException ex) { // notify user } finally { if (oos != null) oos.close(); if (fos != null) fos.close(); }
问题是Netbeans告诉我resource.close()
行抛出一个IOException
,因此必须被捕获或声明。 它也抱怨oos
和fos
可能还没有初始化(尽pipe没有检查)。
这似乎有点奇怪,看到整个问题是如何停止IOException
。
我的膝盖修复是这样做的:
} finally { try { if (oos != null) oos.close(); if (fos != null) fos.close(); } catch (IOException ex) { } }
但是内心深处感到困扰,感觉很肮脏。
我来自一个C#的背景,在那里我只是利用一个using
块,所以我不确定什么是“正确”的方式来处理这个问题。
什么是正确的方式来处理这个问题?
如果您试图从源头上捕获并报告所有exception,则更好的解决scheme是:
ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream(file)); oos.writeObject(shapes); oos.flush(); } catch (FileNotFoundException ex) { // complain to user } catch (IOException ex) { // notify user } finally { if (oos != null) { try { oos.close(); } catch (IOException ex) { // ignore ... any significant errors should already have been // reported via an IOException from the final flush. } } }
笔记:
- 标准的Java包装stream,读取器和编写器都会
close
并flush
到包装stream中,等等。因此,只需要closures或刷新最外层的包装器。 - 在try块末尾显式刷新的目的是为了使
IOException
的(实)处理程序能够看到任何写入失败1 。 - 当您在输出stream上执行closures或刷新操作时,出现“蓝色月亮一次”的机会,由于光盘错误或文件系统已满,可能会抛出exception。 你不应该挤压这个exception! 。
如果你经常需要“closures一个可能为空的stream忽略IOExceptions”,那么你可以写一个像这样的帮助器方法:
public void closeQuietly(Closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (IOException ex) { // ignore } } }
那么你可以用前面的finally代替块:
} finally { closeQuietly(oos); }
(另一个答案指出,在一个Apache Commons库中已经有一个closeQuietly
方法了…如果你不介意为一个10行方法添加一个依赖项到你的项目中)。
但要小心,只有在IOexception真的无关紧要的情况下才能使用closeQuietly
。
1 – 使用试用资源时,这不是必需的。
在人们所问的关于flush()
和close()
的问题上:
- 标准的“filter”和“缓冲”输出stream和写入器有一个API合约,指出
close()
会导致所有缓冲输出被刷新。 您应该发现所有其他(标准)输出缓冲的输出类将以相同的方式运行。 因此,对于一个标准类来说,在close()
之前立即调用flush()
是多余的。 - 对于自定义和第三方类,您需要调查(例如,阅读javadoc,查看代码),但是任何不刷新缓冲数据的
close()
方法都可能被打破 。 -
最后是什么
flush()
实际上做的问题。 javadoc所说的是(对于OutputStream
…)如果此stream的预期目的地是由底层操作系统提供的抽象(例如文件),则刷新该stream只保证先前写入该stream的字节被传递到操作系统进行写入; 它并不保证它们实际写入到物理设备(如磁盘驱动器)中。
所以…如果你希望/想象调用
flush()
保证你的数据一直存在, 你错了! (如果你需要做这样的事,看看FileChannel.force
方法…)
另一方面,如果您可以使用Java 7或更高版本,@Mike Clark的答案中所述的“新”尝试资源是最好的解决scheme。
如果你的新代码没有使用Java 7或更高版本,那么你可能会陷入困境并深入挖掘。
try / catch / finally包含可closures对象(例如Files)的当前最佳实践是使用Java 7的try-with-resource语句,例如:
try (FileReader reader = new FileReader("ex.txt")) { System.out.println((char)reader.read()); } catch (IOException ioe) { ioe.printStackTrace(); }
在这种情况下,FileReader会在try语句结束时自动closures,而不需要在显式finally块中closures它。 这里有几个例子:
http://ppkwok.blogspot.com/2012/11/java-cafe-2-try-with-resources.html
官方的Java描述在:
http://docs.oracle.com/javase/7/docs/technotes/guides/language/try-with-resources.html
Java 7将添加自动资源pipe理块。 它们与C# using
非常相似。
Josh Bloch写了技术build议 ,我强烈推荐阅读。 不仅仅是因为它会给你一个即将到来的Java 7语言特性的支持,而且因为规范激发了对这样一个构造的需求,并且这样做,说明了如何在没有ARM的情况下编写正确的代码。
下面是一个Asker代码的例子,翻译成ARMforms:
try (FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(shapes); } catch (FileNotFoundException ex) { // handle the file not being found } catch (IOException ex) { // handle some I/O problem }
我通常有小classIOUtil与方法如:
public static void close(Closeable c) { if (c != null) { try { c.close(); } catch (IOException e) { // ignore or log } } }
这个家伙呢? 没有空的检查,并不奇怪。 一切都在退出时清理。
try { final FileOutputStream fos = new FileOutputStream(file); try { final ObjectOutputStream oos = new ObjectOutputStream(fos); try { oos.writeObject(shapes); oos.flush(); } catch(IOException ioe) { // notify user of important exception } finally { oos.close(); } } finally { fos.close(); } } catch (FileNotFoundException ex) { // complain to user } catch (IOException ex) { // notify user }
不幸的是,没有语言水平的支持。 但是有很多这样的库使得这个简单。 检查commons-io库。 或现代google-guava @ http://guava-libraries.googlecode.com/svn/trunk/javadoc/index.html
这不是对你的观点的直接回答,但是这是一个不幸的事实,因为finally
和catch
都与try
人联系在一起。 try
块的最好的devise是要么有一个或一个finally
但不是两个。
在这种情况下,您的评论暗示有些事情是错误的。 为什么在处理文件IO的方法中,我们向用户抱怨什么? 我们可能正在某个服务器的某个地方深处运行,并且有一个用户可以看到。
所以,上面出现的代码应该有一个finally
在出现问题时优雅地失败。 它缺乏聪明地处理错误的能力,所以你的catch
属于呼叫链上的某个地方。
你做对了。 它也困扰我的垃圾。 你应该初始化这些stream显式为空 – 这是常见的约定。 你所能做的就是join俱乐部,并希望using
。