递归
对于下面这个递归求阶乘调用
private static long factorialRecursive(long n) {
return n == 1 ? 1 : n * factorialRecursive(n - 1);
}
factorialRecursive调用在运行的时候每次执行factorialRecursive方法调用都会在调用栈上创建一个新的栈帧,用于保存每个方法调用的状态也就是它需要需要进行n * factorialRecursive(n - 1)时候需要保存上一步(暂且叫做A函数)调用,在factorialRecursive(n - 1)(暂且叫做B)执行完成之前,在B完成之前,A会一直持有B的调用记录,而在执行B的时候,又需要执行factorialRecursive(n - 1)(暂且叫做C),像ABC这样的调用记录也叫做”调用栈”.这个操作会一直到程序执行结束.这个过程比迭代执行单一机器级的分支指令大很多,当调用栈太长会造成StackOverflowError
递归调用栈过程如下图
尾递归操作
为了解决递归的这个问题,尾递归调用就出现了.
尾递归就是操作的最后一步[只]调用自身的操作,它不需要保存每次递归计算的中间值,编译器就能自行决定复用某个栈帧进行计算
比如
function f(x) {
if (x === 1) return 1;
return f(x-1);
}
这是尾递归,它不要保存上一步计算结果
function f(x) {
if (x === 1) return 1;
return 1 + f(x-1);
}
上面这段代码这不是尾递归,因为下一步操作链仍然要参考上一步调用链
我们把求阶乘的递归调用改成尾递归调用
private static long factorialRecursive(long result, long n) {
if (n == 1)
return result;
return factorialRecursive(result * n, n - 1);
}
总的来说,尾递归比线性递归多一个参数,这个参数是上一次调用函数得到的结果;所以,关键点在于,尾递归每次调用都在收集结果,避免了线性递归不收集结果只能依次展开消耗内存的坏处。
但是目前java编译器仍然不支持尾递归优化,上面写到的java尾递归只是用于示范