【问题标题】:throws Exception in finally blocks在 finally 块中抛出异常
【发布时间】:2010-10-03 15:29:42
【问题描述】:

是否有一种优雅的方式来处理finally 块中抛出的异常?

例如:

try {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}
finally {
   try{
     resource.close();
   }
   catch( Exception ex ) {
     // Could not close the resource?
   }
}

如何避免finally 块中的try/catch

【问题讨论】:

    标签: java exception try-catch finally


    【解决方案1】:

    我遇到过类似的情况,我无法对资源使用 try,但我也想处理来自关闭的异常,而不仅仅是像 closeQuietly 机制那样记录并忽略它。就我而言,我实际上并没有处理输出流,因此关闭失败比简单的流更有趣。

    IOException ioException = null;
    try {
      outputStream.write("Something");
      outputStream.flush();
    } catch (IOException e) {
      throw new ExportException("Unable to write to response stream", e);
    }
    finally {
      try {
        outputStream.close();
      } catch (IOException e) {
        ioException = e;
      }
    }
    if (ioException != null) {
      throw new ExportException("Unable to close outputstream", ioException);
    }
    

    【讨论】:

      【解决方案2】:

      Resourcebest answer 更改为 Closeable

      Streams 实现了Closeable,因此您可以对所有流重用该方法

      protected void closeQuietly(Closeable resource) {
          if (resource == null) 
              return;
          try {
              resource.close();
          } catch (IOException e) {
              //log the exception
          }
      }
      

      【讨论】:

        【解决方案3】:

        从 Java 7 开始,您不再需要在 finally 块中显式关闭资源,而是可以使用 try-with-resources 语法。 try-with-resources 语句是声明一个或多个资源的 try 语句。资源是程序完成后必须关闭的对象。 try-with-resources 语句确保每个资源在语句结束时关闭。任何实现 java.lang.AutoCloseable 的对象,包括所有实现 java.io.Closeable 的对象,都可以用作资源。

        假设以下代码:

        try( Connection con = null;
             Statement stmt = con.createStatement();
             Result rs= stmt.executeQuery(QUERY);)
        {  
             count = rs.getInt(1);
        }
        

        如果发生任何异常,close 方法将在这三个资源中的每一个上以与它们创建时相反的顺序被调用。这意味着将首先为 ResultSetm 调用 close 方法,然后是 Statement,最后是 Connection 对象。

        知道自动调用 close 方法时发生的任何异常都会被抑制,这一点也很重要。这些被抑制的异常可以通过 Throwable 类中定义的 getsuppressed() 方法检索。

        来源:https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

        【讨论】:

        • 这个答案似乎不完整,没有提到这种方法之间的行为差​​异以及 OP 发布的示例代码如何工作。
        • 如果 try 块中的部分正常完成但 close 方法没有,则使用 try-with-resources 会在关闭时引发异常,这与 OP 代码所做的不同。在不承认行为变化的情况下推荐它作为替代品似乎具有误导性。
        • 不抛出异常,close方法自动调用被抑制。
        • 试试我描述的情况。 try 块正常完成,close 抛出一些东西。并重新阅读您发布链接的页面,仅当 try 块抛出某些内容时才适用抑制。
        【解决方案4】:

        我通常使用org.apache.commons.io.IOUtils 中的closeQuietly 方法之一:

        public static void closeQuietly(OutputStream output) {
            try {
                if (output != null) {
                    output.close();
                }
            } catch (IOException ioe) {
                // ignore
            }
        }
        

        【讨论】:

        • 您可以使用 Closeable public static void closeQuietly(Closeable closeable) { 使此方法更通用
        • 是的,Closeable 很好。可惜很多东西(比如 JDBC 资源)没有实现它。
        【解决方案5】:

        忽略出现在“finally”块中的异常通常是一个坏主意,除非知道这些异常是什么以及它们代表什么条件。在正常的try/finally 使用模式中,try 块将事物置于外部代码不期望的状态,finally 块将这些事物的状态恢复为外部代码期望的状态。捕获异常的外部代码通常会期望,尽管出现异常,但一切都已恢复到normal 状态。例如,假设某些代码启动了一个事务,然后尝试添加两条记录; “finally”块执行“如果未提交则回滚”操作。调用者可能已准备好在执行第二个“添加”操作期间发生异常,并且可能期望如果它捕获此类异常,则数据库将处于尝试任一操作之前的状态。但是,如果在回滚期间发生第二个异常,如果调用者对数据库状态做出任何假设,则可能会发生不好的事情。回滚失败代表了一个重大危机——不应被期望仅仅“添加记录失败”异常的代码捕获。

        我个人倾向于使用 finally 方法捕获发生的异常并将它们包装在“CleanupFailedException”中,认识到此类失败代表一个主要问题,不应轻易捕获此类异常。

        【讨论】:

          【解决方案6】:

          如果您使用的是 Java 7,并且 resource 实现了 AutoClosable,您可以这样做(以 InputStream 为例):

          try (InputStream resource = getInputStream()) {
            // Use the resource.
          }
          catch( Exception ex ) {
            // Problem with the resource.
          }
          

          【讨论】:

            【解决方案7】:

            经过大量考虑,我发现以下代码最好:

            MyResource resource = null;
            try {
                resource = new MyResource();
                resource.doSomethingFancy();
                resource.close(); 
                resource = null;  
            } finally {
                closeQuietly(resource)
            }
            
            void closeQuietly(MyResource a) {
                if (a!=null)
                    try {
                         a.close();
                    } catch (Exception e) {
                         //ignore
                    }
            }
            

            该代码保证如下:

            1. 代码完成后资源被释放
            2. 关闭资源时抛出的异常在不处理的情况下不会被消耗。
            3. 代码不会尝试关闭资源两次,不会产生不必要的异常。

            【讨论】:

            • 你也可以避免调用 resource.close();在 try 块中的 resource = null,这就是 finally 块的用途。另请注意,您不会处理“做一些花哨的事情”时引发的任何异常,实际上,我认为我更喜欢在堆栈的更高应用程序级别处理基础设施异常。
            • resource.close() 也可能抛出异常 - 即当缓冲区刷新失败时。永远不应使用此异常。但是,如果由于先前引发的异常而关闭流,则应悄悄关闭资源,忽略异常并保留根本原因。
            【解决方案8】:

            可以说有点过头了,但如果你让异常冒泡并且你不能从你的方法中记录任何东西(例如,因为它是一个库,你宁愿让调用代码处理异常和记录):

            Resource resource = null;
            boolean isSuccess = false;
            try {
                resource = Resource.create();
                resource.use();
                // Following line will only run if nothing above threw an exception.
                isSuccess = true;
            } finally {
                if (resource != null) {
                    if (isSuccess) {
                        // let close throw the exception so it isn't swallowed.
                        resource.close();
                    } else {
                        try {
                            resource.close();
                        } catch (ResourceException ignore) {
                            // Just swallow this one because you don't want it 
                            // to replace the one that came first (thrown above).
                        }
                    }
                }
            }
            

            更新:我对此进行了更多研究,发现一个比我更清楚地考虑这一点的人写了一篇很棒的博客文章:http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make-mess-of-stream.html 他更进一步,将两个例外合二为一,我可以看到在某些情况下很有用。

            【讨论】:

            • +1 用于博客链接。此外,我至少会记录 ignore 异常
            【解决方案9】:

            我通常这样做:

            try {
              // Use the resource.
            } catch( Exception ex ) {
              // Problem with the resource.
            } finally {
              // Put away the resource.
              closeQuietly( resource );
            }
            

            其他地方:

            protected void closeQuietly( Resource resource ) {
              try {
                if (resource != null) {
                  resource.close();
                }
              } catch( Exception ex ) {
                log( "Exception during Resource.close()", ex );
              }
            }
            

            【讨论】:

            • 是的,我使用了一个非常相似的成语。但我没有为此创建函数。
            • 如果你需要在同一个类的几个地方使用成语,一个函数很方便。
            • 对 null 的检查是多余的。如果资源为空,那么调用方法被破坏应该被修复。此外,如果资源为空,则可能应该记录。否则会导致潜在的异常被静默忽略。
            • 对 null 的检查并不总是多余的。将 "resource = new FileInputStream("file.txt")" 视为尝试的第一行。此外,这个问题与许多人不使用的面向方面编程无关。然而,不应仅仅忽略 Exception 的概念通过显示日志语句得到了最简洁的处理。
            • Resource => Closeable?
            【解决方案10】:
            try {
                final Resource resource = acquire();
                try {
                    use(resource);
                } finally {
                    resource.release();
                }
            } catch (ResourceException exx) {
                ... sensible code ...
            }
            

            工作完成。没有空测试。单次捕获,包括获取和释放异常。当然,您可以使用 Execute Around 习惯用法,并且只需为每种资源类型编写一次。

            【讨论】:

            • 如果 use(resource) 抛出异常 A 然后 resource.release() 抛出异常 B 怎么办?异常 A 丢失了...
            【解决方案11】:

            我通常这样做:

            MyResource r = null;
            try { 
               // use resource
            } finally {   
                if( r != null ) try { 
                    r.close(); 
                } catch( ThatSpecificExceptionOnClose teoc ){}
            }
            

            基本原理:如果我用完了资源,唯一的问题就是关闭它,我就无能为力了。如果我用完资源,杀死整个线程也没有意义。

            这是一种情况,至少对我来说,忽略检查的异常是安全的。

            直到今天我在使用这个成语时还没有遇到任何问题。

            【讨论】:

            • 我会记录下来,以防您将来发现一些漏洞。这样你就知道他们可能(不)来自哪里
            • @Egwor。我同意你的看法。这只是一些快速的smippet。我也记录了它,并且可能使用 catch 是可以用异常来完成的:)
            【解决方案12】:

            您可以将其重构为另一种方法...

            public void RealDoSuff()
            {
               try
               { DoStuff(); }
               catch
               { // resource.close failed or something really weird is going on 
                 // like an OutOfMemoryException 
               }
            }
            
            private void DoStuff() 
            {
              try 
              {}
              catch
              {
              }
              finally 
              {
                if (resource != null) 
                {
                  resource.close(); 
                }
              }
            }
            

            【讨论】:

              【解决方案13】:

              为什么要避免附加块?由于 finally 块包含可能引发异常的“正常”操作,并且您希望 finally 块完全运行,您必须捕获异常。

              如果您不希望 finally 块抛出异常并且您不知道如何处理异常(您只会转储堆栈跟踪)让异常在调用堆栈中冒泡(删除 try-catch从 finally 块)。

              如果你想减少打字,你可以实现一个“全局”外部 try-catch 块,它将捕获 finally 块中抛出的所有异常:

              try {
                  try {
                      ...
                  } catch (Exception ex) {
                      ...
                  } finally {
                      ...
                  }
              
                  try {
                      ...
                  } catch (Exception ex) {
                      ...
                  } finally {
                      ...
                  }
              
                  try {
                      ...
                  } catch (Exception ex) {
                      ...
                  } finally {
                      ...
                  }
              } catch (Exception ex) {
                  ...
              }
              

              【讨论】:

              • -1 这个也是。如果你试图在一个 finally 块中关闭多个资源怎么办?如果关闭第一个资源失败,一旦抛出异常,其他资源将保持打开状态。
              • 这就是为什么我告诉保罗,如果你想确保 finally 块完成,你必须捕获异常。请阅读完整的答案!
              【解决方案14】:

              如果可以的话,你应该测试一下,以避免一开始就出现错误情况。

              try{...}
              catch(NullArgumentException nae){...}
              finally
              {
                //or if resource had some useful function that tells you its open use that
                if (resource != null) 
                {
                    resource.Close();
                    resource = null;//just to be explicit about it was closed
                }
              }
              

              此外,您可能应该只捕获可以从中恢复的异常,如果您无法恢复,则让它传播到程序的顶层。如果您无法测试错误情况,您将不得不像您已经完成的那样用 try catch 块包围您的代码(尽管我建议仍然捕获特定的预期错误)。

              【讨论】:

              • 测试错误条件通常是一种很好的做法,只是因为异常很昂贵。
              • “防御性编程”是一种过时的范式。因测试所有错误条件而产生的臃肿代码最终导致的问题多于解决的问题。 TDD 和处理异常是现代方法恕我直言
              • @Joe - 我不反对你测试所有错误条件,但有时它是有道理的,特别是考虑到简单检查的成本差异(通常)以避免异常与异常本身。
              • -1 在这里,resource.Close() 可以抛出异常。如果您需要关闭其他资源,异常会导致函数返回并且它们将保持打开状态。这就是 OP 中第二次 try/catch 的目的。
              • @Outlaw - 如果 Close 抛出异常,并且资源是打开的,然后通过捕获和抑制异常,你就错过了我的观点,我该如何解决这个问题?因此,我让它传播的原因(我很少能在它仍然打开的情况下恢复)。
              【解决方案15】:

              一个解决方案,如果两个异常是两个不同的类

              try {
                  ...
                  }
              catch(package1.Exception err)
                 {
                  ...
                 }
              catch(package2.Exception err)
                 {
                 ...
                 }
              finally
                {
                }
              

              但有时你无法避免第二次尝试捕捉。例如用于关闭流

              InputStream in=null;
              try
               {
               in= new FileInputStream("File.txt");
               (..)// do something that might throw an exception during the analysis of the file, e.g. a SQL error
               }
              catch(SQLException err)
               {
               //handle exception
               }
              finally
               {
               //at the end, we close the file
               if(in!=null) try { in.close();} catch(IOException err) { /* ignore */ }
               }
              

              【讨论】:

              • 在您的情况下,如果您使用“using”语句,它应该清理资源。
              • 我的错,我假设它是 C#。
              猜你喜欢
              • 2012-09-20
              • 1970-01-01
              • 1970-01-01
              • 2013-10-02
              • 1970-01-01
              • 1970-01-01
              • 2013-02-23
              • 2015-10-21
              相关资源
              最近更新 更多