从Java中的finally块返回

问题描述 投票:0回答:7

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

似乎很多人都认为按照“不要在finally子句中返回”中描述的那样做是一件坏事。再深入一点,我还发现“Java 的返回并不总是”,它显示了finally 块中其他类型的流程控制的一些非常可怕的示例。

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

java exception return try-catch-finally
7个回答
168
投票

几年前我很难找到由此引起的错误。代码是这样的:

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

发生的事情是在其他代码中引发了异常。它被捕获并记录并在

somethingThatThrewAnException()
方法中重新抛出。但异常并没有传播到过去
problemMethod()
。经过很长时间的研究,我们终于找到了 return 方法。 finally 块中的 return 方法基本上阻止了 try 块中发生的异常向上传播,即使它没有被捕获。

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


95
投票

您提供的示例足以让您使用finally中的流量控制。

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


24
投票

如果使用 -Xlint:finally,javac 会在 finally 中警告 return。最初 javac 没有发出警告 - 如果代码有问题,它应该无法编译。不幸的是,向后兼容性意味着无法禁止意想不到的巧妙愚蠢行为。

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


13
投票

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

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

任何影响函数返回的内容都应该位于 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 中,可以从方法“返回”,只需在末尾留下一个值(不返回)。如果您取消注释finally语句中的runningThreads.remove(..)行,您认为会发生什么 - 这会覆盖常规返回值(“OK”)并覆盖异常吗?!


3
投票

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

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

根据Java语言规范:

如果 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 语句总是突然完成。


0
投票

在编程中你可以做成千上万的事情,所以我认为“在finally块中返回是不好的”这句话有点肤浅。
例如,我有一个案例,带有 thymeleaf 模板的 Spring Boot 应用程序,我有一个端点,我必须在其中执行某些操作,无论是成功还是失败/失败,我仍然希望始终返回相同的值,即表示 thymeleaf 的字符串模板
我希望在两种情况下都显示相同的页面,这样我就可以通知用户失败原因,或者确认一切顺利,在这种情况下我认为在finally中返回是完全可以接受的

@PostMapping("/v1/somepage")
  public String doSomething(Model model) {
    try {
      //doWhatever needs to be done
      model.addAttribute("successArg", true);
    } catch (Exception e) {
      model.addAttribute("successArg", false);
      model.addAttribute(error, ExceptionUtils.getMessage(e)));
      logger.error("Exception caught while doing whatever \n {}", ExceptionUtils.getStackTrace(e));
    } finally {
      return "somepage";
    }
  }
© www.soinside.com 2019 - 2024. All rights reserved.