从 Java 中的 finally 块返回

Posted

技术标签:

【中文标题】从 Java 中的 finally 块返回【英文标题】:Returning from a finally block in Java 【发布时间】:2010-09-08 01:34:17 【问题描述】:

我最近惊讶地发现,在 Java 的 finally 块中可以有一个 return 语句。

似乎很多人认为像“Don't return in a finally clause”中所述那样做是一件坏事。再深入一点,我还发现了“Java's return doesn't always”,它显示了 finally 块中其他类型的流控制的一些非常可怕的例子。

所以,我的问题是,谁能给我一个例子,让 finally 块中的 return 语句(或其他流控制)产生更好/更易读的代码?

【问题讨论】:

【参考方案1】:

finally 块内部返回将导致exceptions 丢失。

finally 块中的 return 语句将导致 try 或 catch 块中可能抛出的任何异常被丢弃。

根据Java Language Specification:

如果 try 块的执行由于任何其他原因突然完成 R,然后执行finally块,然后有一个选择:

   If the finally block completes normally, then the try statement
   completes  abruptly for reason R.

   If the finally block completes abruptly for reason S, then the try
   statement  completes abruptly for reason S (and reason R is
   discarded).

注意:根据JLS 14.17 - return 语句总是突然完成。

【讨论】:

【参考方案2】:

几年前我很难找到一个由此引起的错误。代码类似于:

Object problemMethod() 
    Object rtn = null;
    try 
        rtn = somethingThatThrewAnException();
    
    finally 
        doSomeCleanup();
        return rtn;
    

发生的情况是异常在其他代码中被抛出。它在somethingThatThrewAnException() 方法中被捕获、记录和重新抛出。但是异常并没有传播到problemMethod() 之后。经过很长时间的研究,我们终于找到了返回方法。 finally 块中的 return 方法基本上是阻止 try 块中发生的异常传播,即使它没有被捕获。

正如其他人所说,虽然根据 Java 规范从 finally 块返回是合法的,但这是一件坏事,不应该这样做。

【讨论】:

那么应该把退货放在哪里呢? @parsecer 我会在 try 块内调用 somethingThatThrewAnException() 后立即说 @parsecer, ??在 finally 之后按照通常的方式进行操作即可。【参考方案3】:

一个简单的 Groovy 测试:

public class Instance 

  List<String> runningThreads = new ArrayList<String>()

  void test(boolean returnInFinally) 

    println "\ntest(returnInFinally: $returnInFinally)"
    println "--------------------------------------------------------------------------"
    println "before execute"
    String result = execute(returnInFinally, false)
    println "after execute -> result: " + result
    println "--------------------------------------------------------------------------"

    println "before execute"
    try 
      result = execute(returnInFinally, true)
      println "after execute -> result: " + result
     catch (Exception ex) 
      println "execute threw exception: " + ex.getMessage()
      
    println "--------------------------------------------------------------------------\n"

  

  String execute(boolean returnInFinally, boolean throwError) 
      String thread = Thread.currentThread().getName()
      println "...execute(returnInFinally: $returnInFinally, throwError: $throwError) - thread: $thread"
      runningThreads.add(thread)
      try 
        if (throwError) 
          println "...error in execute, throw exception"
          throw new Exception("as you liked :-)")
        
        println "...return 'OK' from execute"
        return "OK"
       finally 
        println "...pass finally block"
        if (returnInFinally) return "return value from FINALLY ^^"
        // runningThreads.remove(thread)
      
  


Instance instance = new Instance()
instance.test(false)
instance.test(true)

输出:

test(returnInFinally: false)
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: false, throwError: false) - thread: Thread-116
...return 'OK' from execute
...pass finally block
after execute -> result: OK
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: false, throwError: true) - thread: Thread-116
...error in execute, throw exception
...pass finally block
execute threw exception: as you liked :-)
-----------------------------------------------------------------------------


test(returnInFinally: true)
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: true, throwError: false) - thread: Thread-116
...return 'OK' from execute
...pass finally block
after execute -> result: return value from FINALLY ^^
-----------------------------------------------------------------------------
before execute
...execute(returnInFinally: true, throwError: true) - thread: Thread-116
...error in execute, throw exception
...pass finally block
after execute -> result: return value from FINALLY ^^
-----------------------------------------------------------------------------

问题:

对我来说有趣的一点是看看 Groovy 如何处理隐式返回。在 Groovy 中,可以从方法中“返回”,只需在末尾留下一个值(不返回)。如果您取消注释 finally 语句中的 runningThreads.remove(..) 行,您认为会发生什么 - 这会覆盖常规返回值(“OK”)并覆盖异常吗?!

【讨论】:

【参考方案4】:

添加控制结构并返回 finally 块只是“仅仅因为你可以”滥用的另一个例子,这种滥用几乎遍布所有开发语言。 Jason 正确地暗示它很容易成为维护的噩梦——反对函数提前返回的论点更适用于这种“延迟返回”的情况。

最后,块的存在有一个目的,无论前面的所有代码中发生了什么,都可以让您完全整理自己。主要是关闭/释放文件指针、数据库连接等,尽管我可以看到它被拉伸到说添加定制审计。

任何影响函数返回的东西都应该在 try 块中。即使您有一个检查外部状态的方法,执行了一项耗时的操作,然后再次检查该状态以防它变得无效,您仍然希望在 try 中进行第二次检查 - 如果它最终位于内部并且长时间操作失败,然后您将不必要地再次检查该状态。

【讨论】:

【参考方案5】:

如果你使用 -Xlint:finally,javac 将在 finally 中返回警告。最初 javac 没有发出任何警告——如果代码有问题,它应该无法编译。不幸的是,向后兼容意味着无法禁止意料之外的巧妙愚蠢。

可以从 finally 块中抛出异常,但在这种情况下,所展示的行为几乎肯定是您想要的。

【讨论】:

【参考方案6】:

您提供的示例足以从 finally 开始使用流控制。

即使有一个“更好”的人为示例,也要考虑以后必须维护您的代码并且可能不知道其中细微之处的开发人员。那个可怜的开发者甚至可能就是你....

【讨论】:

当然。我想我是在问是否有人可以给我一些真正令人信服的好例子。 @MattSheppard 在 daos 中,我经常会在 try-finally 中记录一个查询的退出

以上是关于从 Java 中的 finally 块返回的主要内容,如果未能解决你的问题,请参考以下文章

从 finally 块返回时 Java 的奇怪行为

Java中,finally在try语句块中的return前执行还是后执行

Java中,finally在try语句块中的return前执行还是后执行

[转] java基础-浅析Java中的final关键字

《Java从小白到大牛》之第14章 异常处理(下)

深入理解Java中的final关键字(转)