考虑以下与 REPL 的交互。首先我们用阶乘方法定义一个类:
scala> class C {
def fact(n: Int, result: Int): Int =
if(n == 0) result
else fact(n - 1, n * result)
}
defined class C
scala> (new C).fact(5, 1)
res11: Int = 120
现在让我们在子类中覆盖它以使超类的答案加倍:
scala> class C2 extends C {
override def fact(n: Int, result: Int): Int = 2 * super.fact(n, result)
}
defined class C2
scala> (new C).fact(5, 1)
res12: Int = 120
scala> (new C2).fact(5, 1)
您对最后一次通话的预期结果是什么?您可能期待 240。但没有:
scala> (new C2).fact(5, 1)
res13: Int = 7680
那是因为当超类的方法进行递归调用时,递归调用会经过子类。
如果覆盖工作使得 240 是正确答案,那么在此处的超类中执行尾调用优化将是安全的。但这不是 Scala(或 Java)的工作方式。
除非一个方法被标记为 final,否则它在进行递归调用时可能不会调用自己。
这就是为什么@tailrec 不起作用的原因,除非方法是最终的(或私有的)。
更新:我建议也阅读其他两个答案(约翰和雷克斯)。