为什么java.util.Arrays中的binarySearch()
方法是使用循环而不是使用递归来实现的?
答案 0 :(得分:7)
tl; dr :因为javac不会进行尾部调用优化。
您可能已经注意到,许多算法本质上都是递归的(二进制搜索就是一个很好的例子)。那么为什么递归在Java中没有得到广泛使用?
第一个原因是递归的性能不如普通循环。其次,一个更重要的原因是Java中的递归不是堆栈安全的。如果您的递归将深入到调用堆栈中,则可能导致StackOverflowError
。这是有问题的,因为您不能真正预测要走的深度,因为它取决于要处理的数据。这样的结果是您的递归函数可能在较小的数据集上正常工作,但会在较大的数据集上炸毁堆栈。
幸运的是,通过将递归调用替换为迭代控制结构,可以将每个递归函数转换为迭代函数,从而使其成为栈安全的,这正是在 java.util.Arrays 中看到的。
还有一个缺点是,迭代版本的算法可能被认为较难推理且可读性较差(但这值得商bat)。
递归也是一种在没有可变状态(循环计数器)的情况下进行“循环”的方法,因此您可以进行纯函数式的迭代(例如, Haskell don't even have loops,它仅取决于递归)。
那么我们可以进行堆栈安全,可读和高性能的递归吗?是的,如果编译器可以优化递归以使代码与循环非常相似。大多数JVM语言编译器(Groovy,Scala,Kotlin,Clojure以及更多)可以执行tail-call optimalization。这意味着递归调用是函数的最后一件事,它将被编译为字节码,这看起来与普通的while循环非常相似。
实际上,有proposal可以向 javac 添加尾部调用优化,但是尚未实现。如果要立即使用堆栈安全递归,则可能需要使用其他JVM语言。
答案 1 :(得分:-1)
因为Java无法像许多其他语言(例如JavaScript,Python,C#等)一样很好地处理递归。它们会在某个时间和某个时间破坏您的内存堆栈,因此您在处理某些内容时不能依赖于递归用这些语言,直到计算出的东西更多了,并且您知道递归不会执行的次数超过一定数目。
@KrzysztofAtłasik说的很对,为了解决诸如lisp,球拍,haskell等语言中的此类问题,您可以始终执行尾递归(递归的一种),这样将执行计算并退出每次迭代都占用堆栈的空间,因此您的堆栈将永远不会崩溃,但是不幸的是Java无法以这种方式工作,它无法让您执行尾部递归优化,并且在Java中使用递归时总是受到迭代的限制。 / p>