java支持并优化尾递归调用吗?

时间:2013-12-29 15:33:29

标签: java scala optimization recursion jvm

假设我得到了一个尾递归的递归函数。

System.out.println( sum(Arrays.asList(0, 1, 2, 3, 4, 5)) );

int sum(List<Integer> integers) {
    if (integers.isEmpty())
        return 0;
    else
        return integers.get(0) + sum(integers.subList(1, integers.size()));
}

我想知道这个函数sum是否会在堆栈上增长,还是会被改为循环(因为它是尾递归函数)?

我刚刚看到Scala检测到这样的调用并对其进行了优化,但这是一般的Scala还是JVM?

4 个答案:

答案 0 :(得分:17)

Java支持尾递归调用,但AFAIK并没有对它们进行优化。我认为Scala编译器只是能够做到这一点,而不是JVM本身。查看Scala中的@tailrec注释,看看编译器能够做什么:)

但无论Java / JVM是否优化了尾递归,你的函数都会比必要的更难优化。

看看这个:

int sum(List<Integer> integers) {
    return sum(integers, 0);
}

int sum(List<Integer> integers, int sumSoFar) {
    if (integers.isEmpty())
        return sumSoFar;
    else
        return sum(
                integers.subList(1, integers.size()),
                sumSoFar + integers.get(0)
        );
}

请参阅,我添加了一个带有远程计算的sum参数的重载sum。这种方式当您在else分支中重复出现时,您不再需要实际的堆栈帧 - 您在递归调用中获得了所有需要的函数参数。

在你的代码片段中,只要递归调用,堆栈帧就可能必须存在。

答案 1 :(得分:5)

Java 和 JVM 目前不支持尾调用

需要进行的基本工作是在 JVM 级别,而不是 Java。有一个缓慢移动的工作线来解决这个问题(最初作为 MLVM 的一部分,现在在 Project Loom 之下)。尽管相对较旧,但此 2007 blog from John Rose 是关于如何更改 JVM 字节码的一个很好的简短概述。我认为尾调用工作被搁置一旁,有利于先完成 invokedynamic(这已经发现了 more tricky design considerations for tail calls)。以下是the latest Project Loom proposal的摘录:

<块引用>

由于向 JVM 添加操作调用堆栈的能力无疑是必需的,因此该项目的目标也是添加一个更轻量级的构造,该构造将允许将堆栈展开到某个点,然后使用给定参数(基本上是有效尾调用的概括)。我们将该功能称为 unwind-and-invoke 或 UAI。


其他一些细节:

  • Java 作为一种语言不太可能自动优化尾调用。这是因为尾调用消除的大多数实现对调用堆栈都有一些不直观的影响(例如,如果您在递归调用中抛出异常,您将不会在堆栈跟踪中看到所有递归调用)。

    JavaScript 是一种尝试自动优化尾调用的语言示例(在 ES2015 中),here is a debrief from the V8 team 解释了这些困难。此后,他们删除了该功能并转而支持提案 that supports tail-call optimization only when it is explicit

    如果 JVM 在字节码级别添加了对尾调用的支持,我推测 Java 也可能支持显式尾调用优化(即以 return 上的注释或函数,甚至可能是一个新的关键字)。

  • Scala 尝试检测和优化尾递归到 JVM 字节码循环中。 Here is a project 承诺对现有的 JVM 字节码执行这种优化。这个想法没有被 Java 采用,因为它有点脆弱和有限:

    1. 相互递归函数变得非常棘手(Scala 的 @tailrec 只是放弃了)
    2. 多态递归不起作用
    3. 广义尾调用显然行不通

答案 2 :(得分:3)

根据2014年4月的article

  

值得注意的是,这不是JVM中的错误。它是一种可以实现的优化,可以帮助使用递归的函数式程序员,这种语言在这些语言中更为常见和正常。我最近在Oracle上与Brian Goetz谈到了这个优化问题,并且他说它是要添加到JVM的一系列事项,但它不是一个高优先级的项目。

答案 3 :(得分:-2)

Java 中 Factorial 的尾递归,空间复杂度为 O(1)

  int tailFactorial(int x){
    return tailFactorial(x, 1);
}

int tailFactorial(int x, int totalFactor){
    if(x == 0){
        return totalFactor;
    }else{
        return tailFactorial(x-1, totalFactor * x);
    }

}