为什么会抛出堆栈溢出异常?

4

尽管它可以进行尾递归优化,但这是否有意义呢?

def areStreamsEqual(stream1: InputStream, stream2: InputStream): Boolean =
{
    val one = stream1.read()
    val two = stream2.read()
    if(one != two)
        false
    else if(one == -1 && two == -1)
        true
    else
        areStreamsEqual(stream1, stream2)
}

有没有办法强制Scala编译器在这里进行尾调用优化?


5
如果使用@tailrec注释,您可以告诉scalac在方法没有启用尾递归优化时抛出错误。(该注释并不能强制启用尾递归优化。) - user166390
2个回答

6
感谢pst关于@tailrec的评论。由于Scala编译器错误消息解释了不优化该方法的原因。
<filename>.scala:64: error: could not optimize @tailrec annotated method: it is neither private nor final so can be overridden
def areStreamsEqual(stream1: InputStream, stream2: InputStream): Boolean =

将方法设为私有即可解决问题。

我猜想在字节码级别上,调用方法有两个指令:virtual_call 和 tail_call。


3
关于你上一个评论,不,尾调用在字节码级别上不被支持。实际上,scalac会将你的递归方法重写为迭代方法。 - Aaron Novstrup
3
字节码级别有四个调用指令:invokestatic, invokespecial, invokevirtual, invokeinterface。第五个指令,即invokedynamic,在Java 7中出现。Scala对于尾递归方法不使用任何这些指令。相反,它将调用转换为一个简单的goto指令,跳到方法的顶部。如果您将该方法编写为while循环而不是递归函数,则生成的字节码完全相同。 - Daniel Spiewak
真的,没有尾调用指令。我发现这个stackoverflow答案很有用,可以解释为什么编译器拒绝优化方法https://dev59.com/FG445IYBdhLWcg3wl7bW - luntain

1

对于任何试图在REPL中重新创建编译器错误的人,您必须像这样将方法包装在一个类中:

class Test {
@annotation.tailrec def areStreamsEqual(stream1: InputStream, stream2: InputStream): Boolean =
{
    val one = stream1.read()
    val two = stream2.read()
    if(one != two)
        false
    else if(one == -1 && two == -1)
        true
    else
        areStreamsEqual(stream1, stream2)
}
}

如果您只是将该方法插入到REPL中,它将被很好地进行尾递归优化,因为类外的方法无法被覆盖。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接