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

时间:2010-11-04 00:28:52

标签: 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();
}

问题是Netbeans告诉我resource.close()行抛出IOException因此必须被捕获或声明。它还抱怨oosfos可能尚未初始化(尽管检查为空)。

这看起来有点奇怪,看到的是如何将IOException停在那里。

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

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

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

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

处理此问题的正确方法?

8 个答案:

答案 0 :(得分: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.
        }
    }
}

注意:

  • 标准的Java包装器流,读取器和编写器都将closeflush传播到它们的包装流等。所以你只需要关闭或刷新最外面的包装器。
  • 在try块结束时显式刷新的目的是让IOException的(实际)处理程序看到任何写入失败 1
  • 当您在输出流上执行关闭或刷新时,会出现“一次蓝月亮”的可能性,即由于光盘错误或文件系统已满而引发异常。 你不应该压制这个例外!

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

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

然后你可以用:

替换前面的finally块
} finally {
    closeQuietly(oos);
}

(另一个答案指出,Apache Commons库中已经有closeQuietly方法...如果您不介意为10行方法添加项目依赖项。 UPDATE < / strong>:请注意,在API的2.6版中不推荐使用这些方法。)

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

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


关于人们询问的flush()close()的问题:

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

      

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

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


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

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

答案 1 :(得分: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();
}

在这种情况下,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

答案 2 :(得分:12)

Java 7将添加Automatic Resource Management块。它们与C#的using非常相似。

Josh Bloch写了the technical proposal,我强烈推荐阅读。这不仅仅是因为它将为您提供即将推出的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
}

答案 3 :(得分:4)

我通常使用以下方法的小类IOUtil:

public static void close(Closeable c) {
    if (c != null) {
        try {
            c.close();
        }
        catch (IOException e) {
            // ignore or log
        }
    }
}

答案 4 :(得分:3)

这家伙怎么样?不是空检查,毫不奇怪。一切都在退出时清理干净。

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
}

答案 5 :(得分:1)

不幸的是,没有语言级支持。但是有很多图书馆让这很简单。检查commons-io库。或现代google-guava @ http://guava-libraries.googlecode.com/svn/trunk/javadoc/index.html

答案 6 :(得分:1)

你做得对。它也困扰了我的废话。您应该明确地将这些流初始化为null - 这是常见的惯例。您所能做的就是加入俱乐部并希望获得using

答案 7 :(得分:0)

不能直接回答您的观点,但由于finallycatch都与try相关联,因此人们认为它们属于一体,这是一个不幸的事实。 try块的最佳设计是catchfinally,而不是两者。

在这种情况下,您的评论暗示出现了问题。为什么在处理文件IO的方法中,我们向用户抱怨任何事情。我们可能会在某个服务器上深入运行,并且看不见用户。

因此,当出现问题时,上面提到的代码应该finally才能正常失败。它缺乏智能处理错误的能力,因此你的catch属于调用链上的某个地方。