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 ,因此必须被捕获或声明。 它也抱怨oosfos可能还没有初始化(尽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,读取器和编写器都会closeflush到包装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

这不是对你的观点的直接回答,但是这是一个不幸的事实,因为finallycatch都与try人联系在一起。 try块的最好的devise是要么有一个或一个finally但不是两个。

在这种情况下,您的评论暗示有些事情是错误的。 为什么在处理文件IO的方法中,我们向用户抱怨什么? 我们可能正在某个服务器的某个地方深处运行,并且有一个用户可以看到。

所以,上面出现的代码应该有一个finally在出现问题时优雅地失败。 它缺乏聪明地处理错误的能力,所以你的catch属于呼叫链上的某个地方。

你做对了。 它也困扰我的垃圾。 你应该初始化这些stream显式为空 – 这是常见的约定。 你所能做的就是join俱乐部,并希望using