41

在 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
    }
}

我更喜欢第一种构造,因为只有一个 catch 块,而且看起来更优雅。有没有理由更喜欢第二种或另一种结构?

更新:如果我指出两者都只抛出 IOExceptions 会有所不同readclose?所以在我看来,如果读取失败,关闭也会因为同样的原因而失败。

4

11 回答 11

26

恐怕第一个例子有一个大问题,就是如果在读取时或读取后发生异常,则finally执行该块。到现在为止还挺好。但是如果fr.close()then 导致另一个异常被抛出呢?这将“胜过”第一个异常(有点像放入return一个finally块),您将丢失有关实际导致问题开始的所有信息。

您的 finally 块应该使用:

IOUtil.closeSilently(fr);

此实用程序方法的作用是:

public static void closeSilently(Closeable c) {
    try { c.close(); } catch (Exception e) {} 
} 
于 2008-10-08T16:09:47.400 回答
7

我总是会选择第一个例子。

如果 close 抛出异常(实际上对于 FileReader 永远不会发生),那么处理该异常的标准方法不是抛出适合调用者的异常吗?关闭异常几乎肯定胜过您在使用资源时遇到的任何问题。如果您对异常处理的想法是调用 System.err.println,则第二种方法可能更合适。

存在应该抛出多远异常的问题。应该总是重新抛出 ThreadDeath,但是 finally 中的任何异常都会阻止它。类似地,Error 应该比 RuntimeException 和 RuntimeException 比检查的异常更远。如果你真的想要,你可以编写代码来遵循这些规则,然后用“execute around”习语抽象它。

于 2008-10-08T16:31:53.457 回答
4

我更喜欢第二个。为什么?如果两者都read()抛出close()异常,则其中一个可能会丢失。在第一个构造中,异常 fromclose()覆盖异常 from read(),而在第二个构造中,异常 fromclose()被单独处理。


从 Java 7 开始,try-with-resources 结构使这变得更加简单。在不关心异常的情况下阅读:

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块,但无需关闭资源。

于 2008-10-08T16:10:48.367 回答
2

如果readclose都抛出异常,则read的异常将隐藏在选项 1 中。因此,第二个选项做了更多的错误处理。

但是,在大多数情况下,仍会首选第一个选项。

  1. 在许多情况下,您无法在它们生成的方法中处理异常,但您仍然必须将流处理封装在该操作中。
  2. 尝试在代码中添加编写器,看看第二种方法有多冗长。

如果需要传递所有生成的异常,可以做

于 2008-10-08T16:37:27.360 回答
1

据我所知,不同之处在于,在不同的层面上存在不同的例外和原因,并且

捕获(异常 e)

掩盖了这一点。多个级别的唯一目的是区分您的异常,以及您将如何处理它们:

try
{
  try{
   ...
  }
   catch(IOException e)
  {
  ..
  }
}
catch(Exception e)
{
  // we could read, but now something else is broken 
  ...
}
于 2008-10-08T15:58:33.537 回答
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);
                }
            }
        }
    }
}

请注意“待处理”异常——这会处理关闭期间抛出的异常会掩盖我们可能真正关心的异常的情况。

finally 会首先尝试从任何装饰流的外部关闭,因此如果您有一个包装 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 和它使用的“待定”变量方法。

于 2008-10-08T16:56:53.623 回答
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()
    {}
}
于 2008-10-08T16:27:30.797 回答
0

第二种方法。

否则,我看不到您从 FileReader 构造函数中捕获异常

http://java.sun.com/j2se/1.5.0/docs/api/java/io/FileReader.html#FileReader(java.lang.String)

公共 FileReader(字符串文件名)抛出 FileNotFoundException

所以,我通常在 try 块中也有构造函数。finally 块在尝试关闭之前检查阅读器是否不为空。

同样的模式也适用于数据源、连接、语句、结果集。

于 2008-10-08T17:58:32.143 回答
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。

于 2008-10-08T19:52:22.270 回答
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); 
    } 
}
于 2008-10-08T23:43:56.437 回答
0

在某些情况下,嵌套的 Try-Catch 是不可避免的。例如,当错误恢复代码本身可以抛出异常时。但是为了提高代码的可读性,您总是可以将嵌套块提取到它自己的方法中。查看这篇博文,了解更多关于嵌套 Try-Catch-Finally 块的示例。

于 2015-04-15T00:04:43.493 回答