以下简单函数是尾递归。但是,它仍会抛出java.lang.StackOverflowError
。谁能帮助我理解为什么我会这样做?
我已尝试使用包含数百万个项目的列表的尾递归函数,但从未得到StackOverflowError
。或者这个函数尾递归不是吗?
def recursion(i: Int): Int = {
if (i == 20000) i
else {
recursion(i + 1)
}
}
recursion(0)
更新:添加@tailrec
确实解决了问题,即使我将限制增加到2147483647.感谢@tiran的回答
答案 0 :(得分:3)
该函数是尾递归的,实际上编译器会按预期对其进行优化。您的示例在我的scala 2.11.2 REPL中运行得很好(我添加了几个零,只是为了更好地证明这一点)
scala> def recursion(i: Int): Int = {
| if (i == 2000000) i
| else {
| recursion(i + 1)
| }
| }
recursion: (i: Int)Int
scala> recursion(0)
res0: Int = 2000000
关于@tailrec
,它无法解决"任何东西。 @tailrec
是一个注释,如果无法对带注释的函数执行尾调用优化(TCO),则会触发编译时错误。
因此,无论@tailrec
注释如何,都会尽可能地执行TCO。
由于您的问题只是重新编译,您的IDE可能处于某种脏状态,编辑只是触发了一个干净的编译。代码从一开始就很好。
答案 1 :(得分:1)
在每种语言中,由于存在本地差异,每次调用函数调用时,都会在名为" stack"的内存区域上创建新的分配。存储有关函数的信息,例如参数和返回地址。当函数返回时,取消分配相应的堆栈块并返回给调用者。
假设您有以下功能:
def f(i:Integer) = {
if (i <= 0) 0
else f(i-1)
}
如果你打电话给f(3),你会得到:
f(3) - &gt;电话 - &gt; f(2) - &gt;电话 - &gt; f(1) - &gt;电话 - &gt; F(0)
和你的堆栈,当它处于最大高度时,看起来像:
stack(f(0))
stack(f(1))
stack(f(2))
stack(f(3))
一旦f(0)调用返回,它的块就会被释放,顺序来说,f(1),f(2),f(3)中的块也会被释放。
程序堆栈在任何时候都可以拥有的最大高度是有限制的。你的函数递归20000次,这超过了这个限制,所以JVM会生气并抛出异常。
添加@tailrec告诉计算机将函数编译为尾递归,即(或多或少)在不分配堆栈空间的while循环中转换它,例如:
def recursion(i:Int) = {
while(i<20000) {
i++
}
i
}
因此,您不会破坏堆栈上限:)
编辑:正如其他人所指出的那样,@ tailrec并没有告诉编译器执行TCO,但是如果不能对该方法执行TCO,则要求编译器失败,从而保证该方法的TCO如果编译成功。