在Java中使用Readers和Streams总是让我感到困惑的一件事是close()
方法可以抛出异常。因为将close方法放在finally块中是个好主意,这需要一些尴尬的情况。我通常使用这种结构:
FileReader fr = new FileReader("SomeFile.txt");
try {
try {
fr.read();
} finally {
fr.close();
}
} catch(Exception e) {
// Do exception handling
}
但我也看到了这种结构:
FileReader fr = new FileReader("SomeFile.txt");
try {
fr.read()
} catch (Exception e) {
// Do exception handling
} finally {
try {
fr.close();
} catch (Exception e) {
// Do exception handling
}
}
我更喜欢第一种结构,因为只有一个捕捉块,它看起来更优雅。是否有理由更喜欢第二种或替代结构?
更新:如果我指出read
和close
都只抛出IOExceptions,会不会有所作为?因此,在我看来,如果读取失败,关闭将因同样的原因而失败。
答案 0 :(得分:26)
我担心第一个例子存在一个大问题,即如果在读取之后或之后发生异常,则执行finally
块。到现在为止还挺好。但是如果fr.close()
然后导致另一个异常被抛出怎么办?这将“胜过”第一个例外(有点像将return
置于finally
块中)而将丢失有关实际导致问题的所有信息开始。
你的finally块应该使用:
IOUtil.closeSilently(fr);
此实用程序方法的用法:
public static void closeSilently(Closeable c) {
try { c.close(); } catch (Exception e) {}
}
答案 1 :(得分:7)
我会一直选择第一个例子。
如果close是抛出一个异常(在实践中对于FileReader永远不会发生),那么标准的处理方式是不是抛出适合调用者的异常?近似的例外几乎肯定胜过你使用资源的任何问题。如果你的异常处理的想法是调用System.err.println。
,第二种方法可能更合适存在一个问题,即应该抛出多少异常。应始终重新抛出ThreadDeath,但finally中的任何异常都会阻止它。类似地,Error应该比RuntimeException和RuntimeException更多地抛出异常,而不是检查异常。如果你真的想要,你可以编写代码来遵循这些规则,然后用“执行”这个习惯来抽象它。
答案 2 :(得分:3)
我更喜欢第二个。为什么?如果read()
和close()
都抛出异常,则其中一个可能会丢失。在第一个结构中,close()
的异常会覆盖read()
的异常,而在第二个结构中,close()
的异常会单独处理。
从Java 7开始,try-with-resources construct使这一点变得更加简单。要在不关心例外的情况下阅读:
try (FileReader fr = new FileReader("SomeFile.txt")) {
fr.read();
// no need to close since the try-with-resources statement closes it automatically
}
有异常处理:
try (FileReader fr = new FileReader("SomeFile.txt")) {
fr.read();
// no need to close since the try-with-resources statement closes it automatically
} catch (IOException e) {
// Do exception handling
log(e);
// If this catch block is run, the FileReader has already been closed.
// The exception could have come from either read() or close();
// if both threw exceptions (or if multiple resources were used and had to be closed)
// then only one exception is thrown and the others are suppressed
// but can still be retrieved:
Throwable[] suppressed = e.getSuppressed(); // can be an empty array
for (Throwable t : suppressed) {
log(suppressed[t]);
}
}
只需要一个try-catch,可以安全地处理所有异常。如果您愿意,您仍然可以添加finally
块,但不需要关闭资源。
答案 3 :(得分:2)
如果 read 和 close 都抛出异常, read 的异常将隐藏在选项1中。所以,第二个选项会更多的错误处理。
但是,在大多数情况下,第一个选项仍然是首选。
如果您需要传递所有生成的例外,it can be done。
答案 4 :(得分:1)
据我所知,不同之处在于,在不同的层面上有不同的例外和原因,以及
catch(例外e)
模糊了这一点。多个级别的唯一要点是区分您的例外情况,以及您将对它们采取的措施:
try
{
try{
...
}
catch(IOException e)
{
..
}
}
catch(Exception e)
{
// we could read, but now something else is broken
...
}
答案 5 :(得分:1)
我通常会做以下事情。首先,定义一个基于模板方法的类来处理try / catch乱码
import java.io.Closeable;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public abstract class AutoFileCloser {
private static final Closeable NEW_FILE = new Closeable() {
public void close() throws IOException {
// do nothing
}
};
// the core action code that the implementer wants to run
protected abstract void doWork() throws Throwable;
// track a list of closeable thingies to close when finished
private List<Closeable> closeables_ = new LinkedList<Closeable>();
// mark a new file
protected void newFile() {
closeables_.add(0, NEW_FILE);
}
// give the implementer a way to track things to close
// assumes this is called in order for nested closeables,
// inner-most to outer-most
protected void watch(Closeable closeable) {
closeables_.add(0, closeable);
}
public AutoFileCloser() {
// a variable to track a "meaningful" exception, in case
// a close() throws an exception
Throwable pending = null;
try {
doWork(); // do the real work
} catch (Throwable throwable) {
pending = throwable;
} finally {
// close the watched streams
boolean skip = false;
for (Closeable closeable : closeables_) {
if (closeable == NEW_FILE) {
skip = false;
} else if (!skip && closeable != null) {
try {
closeable.close();
// don't try to re-close nested closeables
skip = true;
} catch (Throwable throwable) {
if (pending == null) {
pending = throwable;
}
}
}
}
// if we had a pending exception, rethrow it
// this is necessary b/c the close can throw an
// exception, which would remove the pending
// status of any exception thrown in the try block
if (pending != null) {
if (pending instanceof RuntimeException) {
throw (RuntimeException) pending;
} else {
throw new RuntimeException(pending);
}
}
}
}
}
注意“待定”异常 - 这会处理在关闭期间抛出异常会掩盖我们可能真正关心的异常的情况。
最后尝试从任何装饰流的外部关闭,所以如果你有一个包装FileWriter的BufferedWriter,我们首先尝试关闭BuffereredWriter,如果失败,仍然尝试关闭FileWriter本身。
您可以按如下方式使用上述课程:
try {
// ...
new AutoFileCloser() {
@Override protected void doWork() throws Throwable {
// declare variables for the readers and "watch" them
FileReader fileReader = null;
BufferedReader bufferedReader = null;
watch(fileReader = new FileReader("somefile"));
watch(bufferedReader = new BufferedReader(fileReader));
// ... do something with bufferedReader
// if you need more than one reader or writer
newFile(); // puts a flag in the
FileWriter fileWriter = null;
BufferedWriter bufferedWriter = null;
watch(fileWriter = new FileWriter("someOtherFile"));
watch(bufferedWriter = new BufferedWriter(fileWriter));
// ... do something with bufferedWriter
}
};
// .. other logic, maybe more AutoFileClosers
} catch (RuntimeException e) {
// report or log the exception
}
使用这种方法,你永远不必担心try / catch / finally再次处理关闭文件。
如果这对你的使用太重,至少要考虑遵循try / catch和它使用的“待定”变量方法。
答案 6 :(得分:0)
我使用的标准约定是你不能让异常转义为finally块。
这是因为如果异常已经传播,则从finally块抛出的异常将胜过原始异常(因此会丢失)。
在99%的情况下,这不是您想要的,因为原始异常可能是您问题的根源(任何次要异常可能是第一个的副作用,但会模糊您找到原始异常来源的能力,因此真正的问题)。
所以你的基本代码应如下所示:
try
{
// Code
}
// Exception handling
finally
{
// Exception handling that is garanteed not to throw.
try
{
// Exception handling that may throw.
}
// Optional Exception handling that should not throw
finally()
{}
}
答案 7 :(得分:0)
第二种方法。
否则,我没有看到你从FileReader构造函数中捕获异常
http://java.sun.com/j2se/1.5.0/docs/api/java/io/FileReader.html#FileReader(java.lang.String)
public FileReader(String fileName) 抛出FileNotFoundException
所以,我通常在try块中也有构造函数。在尝试关闭之前,finally块检查读取器是否为null。
相同的模式适用于Datasource,Connection,Statement,ResultSet。
答案 8 :(得分:0)
有时嵌套的try-catch不是首选项,请考虑这一点:
try{
string s = File.Open("myfile").ReadToEnd(); // my file has a bunch of numbers
// I want to get a total of the numbers
int total = 0;
foreach(string line in s.split("\r\n")){
try{
total += int.Parse(line);
} catch{}
}
catch{}
这可能是一个不好的例子,但有时你需要嵌套的try-cactch。
答案 9 :(得分:0)
我喜欢@Chris Marshall的方法,但我从不喜欢看到异常被静默吞噬。我认为最好记录异常,特别是如果你不管怎样。
我总是使用实用程序类来处理这些常见的异常,但是我会将它与他的答案区别开来。
我总是会使用记录器(对我来说是log4j)来记录错误等。
IOUtil.close(fr);
对实用方法略有修改:
public static void close(Closeable c) {
try {
c.close();
} catch (Exception e) {
logger.error("An error occurred while closing. Continuing regardless", e);
}
}
答案 10 :(得分:0)
在某些情况下,嵌套的Try-Catch是不可避免的。例如,当错误恢复代码本身可以抛出异常时。但是为了提高代码的可读性,您始终可以将嵌套块提取到自己的方法中。查看this博文,了解有关嵌套Try-Catch-Finally块的更多示例。