在Java中从finally块返回

190

我最近惊讶地发现在Java中,finally块内可以使用return语句。

很多人认为这是一件不好的事情,就像在'不要在finally子句中使用return'中描述的那样。更深入地了解后,我还发现了'Java的return并不总是',其中展示了在finally块中使用其他类型的流控制会产生一些非常可怕的例子。

因此,我的问题是,是否有人能给我提供一个在finally块中使用return语句(或其他流控制)可以产生更好/更易读代码的示例?

6个回答

168

多年前我曾经很难追踪一个由此引起的bug。代码大致如下:

Object problemMethod() {
    Object rtn = null;
    try {
        rtn = somethingThatThrewAnException();
    }
    finally {
        doSomeCleanup();
        return rtn;
    }
}
发生的情况是异常在其他代码中被抛出。它被捕获并记录,并在 somethingThatThrewAnException() 方法中重新抛出。但是异常未能从 problemMethod() 中向上传播。经过长时间的研究,我们最终发现了返回方法的问题。finally 块中的返回方法基本上阻止了 try 块中发生的异常传播,即使它没有被捕获。
正如其他人所说,虽然根据Java规范,在 finally 块中返回是合法的,但这是一件不好的事情,不应该这样做。

1
那么return应该放在哪里呢? - parsecer
1
@parsecer 我会建议在 try 块内调用 somethingThatThrewAnException() 后立即处理异常。 - Tiago Sippert
3
@parsecer,按照通常的方式进行操作,放在finally之后。 - Pacerier

95

你提供的例子已经足以说明,不要使用finally中的流程控制。

即使有一个牵强附会的例子可以表明它的“优越性”,也要考虑维护代码的开发人员,他可能不知道其中的微妙之处。而那个可怜的开发人员甚至可能就是你自己....


6
当然。我猜我问这个问题是为了如果有人能给我一些真正令人信服的好的例子。 - Matt Sheppard
@MattSheppard 在 daos 中,我经常会在 try-finally 中记录查询的退出。 - Blake

24

如果使用-Xlint:finally,javac会警告finally中的return语句。原先,javac不会发出任何警告——如果代码有问题,它应该无法编译。不幸的是,向后兼容性意味着无法禁止未预料到的聪明愚蠢行为。

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


13

在finally{}块中添加控制结构和返回语句只是“因为你可以”滥用的又一个例子,这种滥用几乎遍布所有开发语言。Jason提出它很容易成为维护噩梦的观点是正确的-反对从函数中早期返回的论点更适用于这种“晚期返回”的情况。

Finally块存在的唯一目的是允许您彻底整理自己的代码,不管之前的代码发生了什么。主要是关闭/释放文件指针、数据库连接等,虽然我可以看到它被扩展为添加定制审计。

影响函数返回的任何内容都应该位于try{}块中。即使您有一个方法,在其中检查外部状态,并执行耗时操作,然后再次检查该状态以避免其变为无效,您仍然希望将第二个检查放在try{}块中-如果它在finally{}内,则长时间操作失败,然后您将不必要地再次检查该状态。


7

一个简单的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中,可以仅仅在方法结尾处留下一个值(不需要return语句)就可以“返回”方法。如果您取消注释finally语句中的runningThreads.remove(..)行,会发生什么 - 它会覆盖常规返回值(“OK”)并覆盖异常吗?!


3
finally 块中返回将导致 exceptions 被丢失。
finally 块中的返回语句将导致在 try 或 catch 块中可能抛出的任何异常被丢弃。
根据 Java 语言规范:

If execution of the try block completes abruptly for any other reason R, then the finally block is executed, and then there is a choice:

   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,返回语句总是会突然完成。

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