【问题标题】:Is there a preference for nested try/catch blocks?对嵌套的 try/catch 块有偏好吗?
【发布时间】:2008-10-08 15:53:32
【问题描述】:

在 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 块,而且它看起来更优雅。是否有理由更喜欢第二种或另一种结构?

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

【问题讨论】:

  • 最糟糕的其实是close()方法可以抛出异常。如果发生异常,除了关闭流,你还能做什么?然后关闭它会引发另一个异常,所以你应该关闭流,这将......我不喜欢 Java 做这些愚蠢的事情。
  • @OregonGhost - 如果用户正在处理一组流并且其中一个流无法关闭,会发生什么?这可能意味着所有其他流都处于不可用状态,应该进行测试。如果忽略,则它们可能处于不可用状态,用户继续工作但无法保存。
  • @Rontologist:我的意思是,当抛出异常时,除了关闭它之外,你什么也做不了,那么为什么在关闭时抛出呢? IMO 关闭不应该失败,因为您对此无能为力,也无法摆脱它。关闭后流是坏的,失败与否。在 .NET 中,Close() 不会抛出。
  • @OregonGhost:如果您使用的是有效的单个流。但是,当多个流在播放时,这是触发其他流的健全性检查以确定是否需要用户干预的绝佳机会。
  • @Rontologist:我从未见过这样做的代码。大多数要么在一个流失败时关闭(因为您无法继续,即使其他流都很好),或者它们继续并在其他流坏时从其他流中获取异常。

标签: java try-catch


【解决方案1】:

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

你的 finally 块应该使用:

IOUtil.closeSilently(fr);

这个实用方法的作用:

public static void closeSilently(Closeable c) {
    try { c.close(); } catch (Exception e) {} 
} 

【讨论】:

  • 打败我。一个注意事项:如果您使用的是日志子系统,我会在 closeSilently 中包含一条日志消息,如果发生异常并说明它已被抑制。对异常不采取任何行动可能会在以后隐藏重要的诊断信息。
  • 非常正确。也许省略日志语句是我打败你答案的一个因素?
  • 吞下这样的异常可能很危险。至少你应该在 catch 中记录堆栈跟踪。根据具体情况,将异常包装在运行时异常中可能是更好的方法。
  • @Rontologist:但关键是我们不想从 finally 块中抛出任何异常,所以运行时异常同样糟糕。
  • 这就是为什么我说应该首先记录它。 ;) 但是,我认为简单的吞咽是一种不好的做法。如果可关闭文件是一个文件并且我们未能关闭它,我们可能应该让用户知道。如果是网络连接也一样。它们都表明可能正在发生更大的问题。
【解决方案2】:

我总是选择第一个例子。

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

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

【讨论】:

    【解决方案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 块,但无需关闭资源。

    【讨论】:

      【解决方案4】:

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

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

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

      如果需要传递所有生成的异常,it can be done

      【讨论】:

        【解决方案5】:

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

        捕获(异常 e)

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

        try
        {
          try{
           ...
          }
           catch(IOException e)
          {
          ..
          }
        }
        catch(Exception e)
        {
          // we could read, but now something else is broken 
          ...
        }
        

        【讨论】:

          【解决方案6】:

          我通常会做以下事情。首先,定义一个基于模板方法的类来处理 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 和它使用的“挂起”变量方法。

          【讨论】:

            【解决方案7】:

            我使用的标准约定是你不能让异常逃逸 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()
                {}
            }
            

            【讨论】:

            • 如果在 finally 块中出现 ThreadDeath 怎么办?
            【解决方案8】:

            第二种方法。

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

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

            公共文件读取器(字符串文件名) 抛出 FileNotFoundException

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

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

            【讨论】:

            • 第一个构造函数可以毫无问题地移动到外部 try 块中。可能外部 try 块采用不同的方法。当然,底层流并没有在 FileRead 中关闭,然后在从构造函数返回之前抛出未经检查的异常。
            【解决方案9】:

            有时嵌套的 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。

            【讨论】:

              【解决方案10】:

              我喜欢@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); 
                  } 
              }
              

              【讨论】:

                【解决方案11】:

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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-09-30
                  • 1970-01-01
                  • 2023-03-19
                  • 2018-11-07
                  • 1970-01-01
                  • 2022-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多