Java在获取/关闭资源时尝试/捕获/最终最佳实践

2022-08-31 23:50:24

在处理学校项目时,我编写了以下代码:

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()IOExceptionoosfos

这似乎有点奇怪,因为重点是如何停在那里。IOException

我的下意识解决方法是这样做:

} finally {
    try {
        if (oos != null) oos.close();
        if (fos != null) fos.close();
    } catch (IOException ex) { }
}

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

我来自C#背景,在那里我只是利用一个块,所以我不确定处理这个问题的“正确”方法是什么。using

处理此问题的正确方法是什么


答案 1

请注意,以下内容仅适用于 Java 6 及更早版本。对于 Java 7 及更高版本,您应该切换到使用 try-with-resources ...如其他答案中所述。

如果您尝试在源代码(在 Java 6 或更早版本中)捕获并报告所有异常,更好的解决方案是:

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包装流,读取器和写入器都传播并传播到他们的包装流等。因此,您只需要关闭或冲洗最外层的包装器即可。closeflush
  • 在 try 块的末尾显式刷新的目的是使 (real) 处理程序能够看到任何写入失败1IOException
  • 当您对输出流执行关闭或刷新时,由于光盘错误或文件系统已满,可能会引发“蓝月亮中的一次”异常。你不应该压扁这个例外!

如果您经常需要“关闭可能为空的流,忽略 IOExceptions”,那么您可以为自己编写一个帮助器方法,如下所示:

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

然后,您可以将上一个最终块替换为:

} finally {
    closeQuietly(oos);
}

另一个答案指出,一种方法已经在Apache Commons库中可用......如果您不介意为10行方法的项目添加依赖项。closeQuietly

但请注意,您只在 IO 异常确实无关紧要的流上使用。closeQuietly

更新:在Apache Commons API的2.6版本中已弃用。Java 7+ try-with-resources 使它变得多余。closeQuietly


关于人们在评论中询问的对立问题:flush()close()

  • 标准的“筛选器”和“缓冲”输出流和写入器具有一个 API 协定,该协定声明导致所有缓冲输出被刷新。您应该发现执行输出缓冲的所有其他(标准)输出类的行为方式相同。因此,对于标准类,在 紧接之前调用是多余的。close()flush()close()

  • 对于自定义类和第三方类,您需要进行调查(例如,读取javadoc,查看代码),但是任何不刷新缓冲数据的方法都可以说是坏的close()

  • 最后,还有一个问题,即实际作用是什么。javadoc说的是这个(对于...flush()OutputStream

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

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


答案 2

目前尝试/捕获/最终涉及可关闭对象(例如文件)的最佳实践是使用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 语句的末尾自动关闭,而无需在显式 final 块中关闭它。这里有几个例子:

http://ppkwok.blogspot.com/2012/11/java-café-2-try-with-resources.html

官方的Java描述位于:

http://docs.oracle.com/javase/7/docs/technotes/guides/language/try-with-resources.html


推荐