【问题标题】:Java try-finally return design questionJava try-finally 返回设计问题
【发布时间】:2011-05-10 06:40:42
【问题描述】:

在 Java 中,try { ... } finally { ... } 的执行对我来说有点不直观。如另一个问题Does finally always execute in Java? 所示,如果try 块中有return 语句,如果定义了finally 块,它将被忽略。比如函数

boolean test () {
    try {
        return true;
    }
    finally {
        return false;
    }
}

将始终返回 false。我的问题:为什么会这样? Java 做出的这个设计决策背后是否有特定的哲学?感谢您的任何见解,谢谢。

编辑:我对“为什么”Java 认为可以违反我定义的语义特别感兴趣。如果我在 try 块中“返回”,则该方法应该立即返回。但是 JVM 决定忽略我的指令并从实际上还没有到达的子程序返回。

【问题讨论】:

  • 一个类似的问题或有趣的事情是,如果你有一个没有catch的try-finally,并且你从finally中抛出一个异常,那么原来的异常已经进入了必杀技...... :) 所以永远不会让异常退出 finally 块
  • 不,不会的。您的代码无法编译 ;-)
  • @Peter:只是因为他输入了错误的返回类型。
  • @Gábor:您可能应该从 catch 块中抛出异常,并使用“throw new Exception("Message", throwable)”附加捕获的异常,其中 throwable 是捕获的异常。稍后这将为您提供一个很好的堆栈跟踪。但是如果你在 finally 块中抛出异常,这听起来很奇怪,因为这意味着 try 块永远不会成功;-)
  • 虽然最终允许“return”语句看起来像是一个设计错误,但这并不意味着“Java 违反了您定义的语义”。规则很简单,finally 块总是得到最后一个单词。它不是“尚未达到的子程序”。那是对finally目的的误解。 finally 之所以被添加到现代语言中,正是因为在旧语言中,无法保证“无论如何”总是会执行某些逻辑。在这种情况下,最终“获胜”是一件好事。将“return false”移到 finally 的末尾。

标签: java


【解决方案1】:

从技术上讲,如果 finally 块已定义,则 try 块中的 return 不会被忽略,前提是该 finally 块还包含 return

这是一个可疑的设计决策,可能回想起来是一个错误(很像引用默认为空/可变,并且根据某些检查异常)。在许多方面,这种行为与对finally 含义的通俗理解完全一致——“无论try 块中事先发生了什么,始终运行此代码。”因此,如果您从finally 块中return true,则整体效果必须始终是返回true,不是吗?

一般来说,这很少是一个好的习惯用法,您应该大量使用finally 块来清理/关闭资源,但很少从它们返回值。

【讨论】:

  • 在我看来,如果 finally 块中有 return 语句,Java 甚至会警告你。至少 Eclipse 告诉我“finally 块没有正常完成”。正如 Andrzej 所说,finally 块用于关闭资源。
  • 从技术上讲,在 ISN'T IGNORED 块中返回。返回值设置为真。然后最终“得到最后的发言权”,因为它应该。将返回值设置为 false。同意,规范不应该允许最终返回,以避免这种混淆。可能程序员在这里的意思是在“try/finally”块的(外部)结束之后放置“return false”。
  • 这个规范是一个很好的设计决策。 finally 块是 DRY 设计原则。当我可以在 finally 块中关闭它一次时,我不再需要在每次抛出异常之前关闭我的数据库连接。考虑到这个目的,我很高兴我可以选择在特定情况下在 finally 块中吞下任何异常,这样我就不必在每次抛出异常之前对这种情况进行编码,或者包装我的 try/catch/finally 块另一个 try/catch 块。直到您了解规则(就像 = 与 == 一样)才会令人困惑,并且它使上游逻辑更加简洁。
  • @Marcus - finally 块非常适合,毫无疑问。问题是(被允许)从finally 块中返回一个值 是否是个好主意,我看不出有什么好的理由这样做。
  • @AndrzejDoyle - 我想你错过了我的意思,在 finally 块中返回意味着我可以吞下所有定义的 catch 块中抛出的任何和所有异常。也许我想做的不是运行测试工具,或者它应该发生在一周中的当前日期是星期天时,无论在什么情况下,我都不应该在每次抛出异常之前编写 if 逻辑或包装整个try 块在另一个 try catch 块中,因为这不是很干。
【解决方案2】:

如果 finally 块中的代码突然结束,它会更改 try 块的返回值/异常。这被认为是不好的做法,您不应该这样做。

在其他地方,Java Puzzlers 书中也对此进行了讨论。

【讨论】:

  • 当 C++ 没有 finally 块并且不允许在析构函数中抛出(或更改导致破坏的范围的返回值)时,它怎么可能是 C++ 的遗留物,这是最接近的表亲最后?
  • @JacekSieka:这是一个有趣的问题。我的 C++ 同事告诉我,它在 C++ 中的工作原理是一样的,所以我认为 Java 是从 C++ 中获得的。我不知道他的意思,也许只是一些 c++ 扩展?但是,如果它不是官方 C++ 的一部分,它就不能被剩余 ;-) 我将从我的答案中删除该部分。
【解决方案3】:

如果您使用 -Xlint 调用 javac,则会生成适当的警告,指示您不应从 finally 子句调用 return。例如(用上面的test()方法编译一个简单的类):

javac -Xlint foo.java
foo.java:13: warning: [finally] finally clause cannot complete normally
    }
^
1 warning

【讨论】:

    【解决方案4】:

    finally的目的:

    • Java 人创建了finally 块用于关闭操作,这两种情况都必须执行(例如发生异常或没有异常)。

    • 不建议在 finally 块中使用 return 语句,因为它会覆盖 catchtry 块中的 return 语句。 并警告“finally 块没有正常完成

    • 只有当trycatch 都没有return 语句时,finally 块才应该有 return 语句。

    • 如果trycatch 具有不同的值,那么我们不应该在finally 块中保留任何内容,

    • 如果我们在 trycatch 块中返回相同的值,那么最好在 finally 中保留 return 并在 trycatch 块中删除 return。
    • 如果finally 抛出任何异常,则该块内的return 将不会执行。

    finally 块仅在以下情况下不会执行:

    • 在调用 System.exit() 时
    • 杀死进程
    • 应用程序/环境崩溃
    • stackoverflow/无限循环

    【讨论】:

      【解决方案5】:

      finally 构造是 JLS 提供的一种工具,用于处理容易出现异常的代码。开发人员可以利用此功能确保从异常中正常恢复。以您描述的方式使用 finally 构造并不是一个好的做法。就此而言,从 finally 块中 return 任何内容都不是好事。
      因此,正如您所想,“哲学”是处理任何突然完成的代码。如果需要返回某个值作为处理此类状态的一部分,则应在 catch 块内完成。

      【讨论】:

        【解决方案6】:

        检查此以供参考 Does finally always execute in Java?

        "带有finally块的try语句,先执行try块,然后有一个选择:
        如果 try 块的执行正常完成,[...]
        如果 try 块的执行由于抛出值 V 而突然完成,[...]
        如果 try 块的执行由于任何其他原因 R 突然完成,则执行 finally 块。然后有一个选择:
        如果 finally 块正常完成,则 try 语句由于原因 R 突然完成。
        如果 finally 块由于原因 S 突然完成,则 try 语句由于原因 S 突然完成(并且原因 R 被丢弃)。”

        【讨论】:

          【解决方案7】:

          虽然finally块是用来关闭资源的,一般不应该有return语句,并且Eclipse警告“finally块没有正常完成”,但我发现在某些情况下“finally return”仍然存在可取的。

          ResponseType response = new ResponseType();
          try{
              //set some properties of the response object.
              response.setStatus(1);
              //return response;
          }catch (Exception e){
              //Some other properties of the response object according to the exception.
              response.setStatus(0);
              //return response;
          }finally{
              return response;
          }
          

          如果我不把 return 子句放在 finally 块中,我将不得不在 try 和 catch 块中重复它,并且当前代码更清晰一些。

          【讨论】:

          • 在这种情况下,我宁愿将return 放在finally 之后(然后删除空的finally 块)。干净多了。
          • 那么如果出现异常,你的return就不会运行了。
          • @Starrow Pan:当抛出未捕获的异常时,是否存在方法实际返回东西的情况?
          猜你喜欢
          • 1970-01-01
          • 2014-04-27
          • 2017-07-20
          • 2012-02-10
          • 2016-03-28
          • 2011-03-28
          • 1970-01-01
          • 2011-11-04
          • 2018-11-05
          相关资源
          最近更新 更多