抛出异常来强制退出递归是否有效?

sap*_*apy 5 java recursion tail-recursion

以下只是伪代码。

  function x(node){
    if(node.val == 42) 
       return true; // What if I throw an exception here ?
    val1 = node.left ? x(node.left) : false ;
    val2 = node.right ? x(node.right): false; 
    return val1 || val2 ;
 }
Run Code Online (Sandbox Code Playgroud)

假设我想在一棵树上找到 42。通过上面的代码,如果我返回一个有效值,它将冒泡到整个递归链,然后最终返回。

我的假设是,如果我只是抛出异常而不是在第 3 行返回 true,那么它实际上不会通过递归链向上冒泡并直接返回给调用者。

仅抛出异常来破坏整个链是一个好习惯吗?因为假设在 处line 4,它确实在某个嵌套堆栈中找到了 42,并且它仍然会在 处进行递归line 5。如果我只是在 处抛出异常line 3,我们就可以避免不必要的计算。

问题更多的是编译器内部,如果我只是抛出一个未处理的异常,它是否仍然会冒泡到递归堆栈(这使得这种方法毫无意义)或直接返回到父调用的程序计数器。

tem*_*def 4

由于几个不同的原因,我不建议抛出异常来破坏递归链。

  • 效率。一般来说,抛出异常比从函数返回慢得多。具体来说,当引发异常时,运行时需要使用当前堆栈跟踪填充异常,需要查看要调用哪个处理程序,需要搜索要finally执行的块或try-with-resources 语句,跟踪垃圾对象引用这仍然需要遍历调用堆栈,就像在调用链上返回值一样,而且几乎肯定不会比更标准的方法更快。

  • 可用性。如果您抛出异常来中止递归链,那么调用您的函数的任何人都需要编写对该异常做出反应的代码。这意味着if (myCall()) {...}它们不需要执行诸如 write 之类的操作,而是需要具有单独的分支,一个用于调用正常返回值的分支,另一个用于调用抛出异常的情况。如果他们忘记这样做,则运行时的代码可能会因异常而显得失败,而实际上它的行为却符合预期。更糟糕的是,您实际上会颠倒异常的正常使用。如果你的代码在成功时抛出异常并在失败时返回一个值,那么无论谁读你的代码,都可能会“嗯?” 在弄清楚你的意思之前至少一次。

  • 清晰度。例外应该用于非常特殊的情况!异常机制旨在在根本没有可行的方法返回值的情况下报告回信息。重新利用异常系统来处理常规控制流会使代码更难以阅读和维护,并且违反了最小意外原则。

尽管您最初的问题主要是关于效率点,但我认为其他两点 - 可用性和清晰度 - 仍然强烈支持不以这种方式做事。