在进入 finally 块之前,是否有可能检测到异常已发生?

23
在Java中,是否有一种优雅的方法来检测在运行finally块之前是否发生了异常?在处理"close()"语句时,通常需要在finally块中进行异常处理。理想情况下,我们希望保留两个异常并将它们传播上去(因为它们都可能包含有用的信息)。我能想到的唯一方法是在try-catch-finally范围外部有一个变量来保存抛出的异常的引用。然后,将“保存”的异常与finally块中发生的任何异常一起传播上去。
有没有更加优雅的方法?也许有API调用可以揭示这一点?
以下是大致的代码示例:
Throwable t = null; 
try {   
   stream.write(buffer); 
} catch(IOException e) {
    t = e;   //Need to save this exception for finally
    throw e;
} finally {   
    try {
       stream.close();   //may throw exception
   } catch(IOException e) {
      //Is there something better than saving the exception from the exception block?
      if(t!=null) {
         //propagate the read exception as the "cause"--not great, but you see what I mean.
         throw new IOException("Could not close in finally block: " + e.getMessage(),t);
      } else {
         throw e;  //just pass it up
      }    
   }//end close
}

显然,还有其他类似的折衷方案,可能涉及将异常保存为成员变量,从方法返回异常等等...但我正在寻找更加优雅的解决方案。也许像Thread.getPendingException()这样的东西或类似的东西?就此而言,其他语言中是否有优雅的解决方案呢?
这个问题实际上是由另一个问题中的评论引发的,该评论提出了一个有趣的问题。
5个回答

13
您提到的关于在try/catch/finally之外设置变量的想法是正确的。
一次只能传播一个异常。

6
我建议的做法是,不要使用布尔标志,而应该存储对异常对象的引用。这样,您不仅可以检查是否发生了异常(如果没有异常发生,则对象为空),而且如果确实发生异常,您还可以在finally块中访问异常对象本身。只需要记住在所有catch块中设置错误对象(当且仅当重新抛出错误时)。
我认为这是C#语言缺失的一个功能,应该添加上。finally块应该支持对基本Exception类的引用,类似于catch块的支持,以便在finally块中可用传播异常的引用。这将是编译器的一项简单任务,使我们免去手动创建本地Exception变量并在重新抛出错误之前手动设置其值的麻烦,同时防止我们在不重新抛出错误时设置Exception变量的错误(请记住,我们只想在finally块中显示未捕获的异常)。
finally (Exception main_exception)
{
    try
    {
        //cleanup that may throw an error (absolutely unpredictably)
    }
    catch (Exception err)
    {
        //Instead of throwing another error,
        //just add data to main exception mentioning that an error occurred in the finally block!
        main_exception.Data.Add( "finally_error", err );
        //main exception propagates from finally block normally, with additional data
    }
}

如上所示...我希望在finally块中可以使用异常,因为如果finally块捕获了自己的异常,则可以将错误作为附加数据添加到原始错误,而不是通过抛出新错误来覆盖主要异常(不好)或者忽略错误(同样不好)


1
我想看到的一个相关特性是 IDisposableEx 接口,其中包括一个 IDisposable(Exception ex) 方法,并且当可用时,编译器的 using 块将使用该接口。在许多情况下,如果使用 using 块来封装锁的代码引发异常,则正确的操作不应释放锁,也不应无限期地保留它,而是应将其 作废,以便任何待处理或未来尝试获取锁的尝试都将立即抛出异常。 - supercat

2
你可以在catch(es)中设置一个布尔标志。我不知道有什么“炫酷”的方法来做到这一点,但我更多是.NET方面的人。

有没有在.NET中更加"优雅"的解决方案? - James Schek
1
如果我知道一个,我会提到它的。 - Joel Coehoorn
我认为这也是在.NET中实现它的唯一方法。 - Scott Dorman

2

Use logging...

try {   
   stream.write(buffer); 
} catch(IOException ex) {
    if (LOG.isErrorEnabled()) { // You can use log level whatever you want
        LOG.error("Something wrong: " + ex.getMessage(), ex);
    }
    throw ex;
} finally {   
    if (stream != null) {
        try {
            stream.close();
        } catch (IOException ex) {
            if (LOG.isWarnEnabled()) {
                LOG.warn("Could not close in finally block", ex);
            }
        }
    }
}

1
在vb.net中,可以使用“Catch...When”语句将异常捕获到本地变量中,而无需实际捕获它。这有许多优点。其中包括:
  1. 如果没有任何东西最终会捕获异常,则将从原始异常的位置触发未处理的异常陷阱。比在最后重新抛出时调试器陷阱要好得多,特别是由于可能需要用于调试的信息尚未超出范围或被“finally”语句清除。
  2. 虽然重新抛出不会像“Throw Ex”那样清除堆栈跟踪,但它仍然经常会破坏堆栈跟踪。如果未捕获异常,则堆栈跟踪将保持清洁。

由于VB不支持此功能,编写一个VB包装器以在C中实现代码可能会很有帮助(例如,给定MethodInvoker和Action(Of Exception),在“Try”中执行MethodInvoker,在“Finally”中执行Action)。

有一个有趣的怪癖:Catch-When可能会看到最终子句异常将被覆盖的异常。 在某些情况下,这可能是一件好事; 在其他情况下,这可能会令人困惑。 无论如何,这都是要注意的事情。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接