获取/关闭资源时Java try/catch/finally最佳实践

Aus*_*yde 40 java resources try-catch

在研究学校项目时,我写了以下代码:

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();
}
Run Code Online (Sandbox Code Playgroud)

问题是Netbeans告诉我resource.close()线路抛出IOException,因此必须被捕获或声明.它也抱怨oos并且fos可能尚未初始化(尽管无效检查).

这看起来有点奇怪,看到整个点是如何阻止IOException那里的权利.

我的下意识修复是这样做的:

} finally {
    try {
        if (oos != null) oos.close();
        if (fos != null) fos.close();
    } catch (IOException ex) { }
}
Run Code Online (Sandbox Code Playgroud)

但在内心深处,这让我感到困扰,感觉很脏.

我来自C#背景,我只是利用一个using块,所以我不确定处理这个问题的"正确"方法.

处理这个问题的正确方法什么?

Ste*_*n C 53

如果您尝试从源头捕获并报告所有异常,则更好的解决方案是:

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.
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 标准的Java包装流,读者和作家的所有传播closeflush自己的包裹流等,所以你只需要关闭或刷新最外层的包装.
  • 在try块结束时显式刷新的目的是使(实际)处理程序IOException能够看到任何写入失败1.
  • 当您对输出流执行关闭或刷新时,由于光盘错误或文件系统已满而导致异常将被"抛出一个蓝月亮". 你不应该压制这个例外!.

如果你经常需要"关闭一个忽略IOExceptions的可能为空的流",那么你可以自己编写一个这样的辅助方法:

public void closeQuietly(Closeable closeable) {
    if (closeable != null) {
        try {
            closeable.close();
        } catch (IOException ex) {
            // ignore
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以用以下代码替换之前的finally块:

} finally {
    closeQuietly(oos);
}
Run Code Online (Sandbox Code Playgroud)

(另一个答案指出,一个closeQuietly方法已经在Apache Commons库中可用了......如果你不介意为你的项目添加10行方法的依赖项. 更新:请注意这些方法在版本2.6中不推荐使用API).

但请注意,您只能closeQuietly在IO异常真正无关的流上使用.

1 - 使用try-with-resources时不需要这样做.


论问题flush()close()人们的询问:

  • 标准的"过滤器"和"缓冲"输出流和close()写入器具有API合同,该合同声明会刷新所有缓冲的输出.您应该发现执行输出缓冲的所有其他(标准)输出类的行为方式相同.因此,对于标准类,flush()之前立即调用是多余的close().
  • 对于自定义和第三方类,您需要进行调查(例如,阅读javadoc,查看代码),但任何close()不刷新缓冲数据的方法都可能被破坏.
  • 最后,还有flush()实际问题.javadoc说的是这个(对OutputStream......)

    如果该流的预期目的地是由底层操作系统提供的抽象,例如文件,则刷新流仅保证先前写入流的字节被传递到操作系统以进行写入; 它不能保证它们实际上写入物理设备,如磁盘驱动器.

    所以...如果您希望/想象调用flush()保证您的数据将持续存在,那么您错了! (如果你需要做那种事情,看看FileChannel.force方法......)


另一方面,如果您可以使用Java 7或更高版本,则@Mike Clark的答案中描述的"新"试用资源是最佳解决方案.

如果您没有使用Java 7或更高版本来使用新代码,那么您可能陷入了深深的困境并深入挖掘.

  • 从开始到结束,这似乎太脏了。Java真的很奇怪吗? (2认同)

小智 25

try/catch /最终涉及可关闭对象(例如Files)的当前最佳实践是使用Java 7的try-with-resource语句,例如:

try (FileReader reader = new FileReader("ex.txt")) {
    System.out.println((char)reader.read());
} catch (IOException ioe) {
    ioe.printStackTrace();
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,FileReader会在try语句结束时自动关闭,而不需要在显式finally块中关闭它.这里有几个例子:

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


Mik*_*ark 13

Java 7将添加自动资源管理块.它们与C#非常相似using.

Josh Bloch写了技术提案,我强烈推荐阅读.这不仅仅是因为它将为您提供即将推出的Java 7语言功能,还因为该规范激发了对这种结构的需求,并且这样做,说明了即使在没有ARM的情况下如何编写正确的代码.

这是Asker代码的一个例子,翻译成ARM形式:

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
}
Run Code Online (Sandbox Code Playgroud)

  • @Muddz 不,ARM 不会调用刷新。值得注意的是,以常用方式使用的大多数常见 Java 类不需要您在调用 close() 之前调用 flush(),因为 close() 的低级实现通常做了足够的工作来显式结束-stream 冲洗不必要。不幸的是,这个刷新/关闭合同没有记录,它只是人们普遍信任的一种已知的隐式行为。在您知道需要在 close() 之前调用 flush() 的特定情况下,您可以装饰您的流,例如 `new MyFlushOnCloseOutputStream(originalOutputStream)`。 (2认同)