考虑以下与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 只有在一个方法被标记为 final(或 private)时才有效。
更新:我建议也阅读其他两个答案(John 和 Rex 的答案)。
TCO
,它可以安全地与该方法一起使用,而更严格的tailrec
不能使用,因为该方法可能不是自递归的。 - Tim