0Pricing
Scala for Backend Engineering & Functional Programming · 课时

tailrec 注解

保证优化

tailrec 注解 是 CoddyKit 上的免费 Scala for Backend Engineering & Functional Programming 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Scala for Backend Engineering & Functional Programming 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。

什么是尾递归

当递归调用是函数执行的最后一个动作时,它处于尾部位置。尾递归函数可以被优化为循环,重复使用同一个栈帧,因此不会发生栈溢出。

尾部位置

在 n * factorial(n-1) 中,递归调用不是最后一个动作:它返回后还要执行乘法。在 gcd(b, a % b) 中,调用是最后一个动作。只有后者是尾递归。

@tailrec 注解

导入 scala.annotation.tailrec 并为方法添加注解。编译器随后会验证该调用确实处于尾部位置,并应用优化。如果不是,编译就会失败。

import scala.annotation.tailrec

object Main {
  @tailrec
  def countdown(n: Int): Unit = {
    if (n >= 0) {
      println(n)
      countdown(n - 1)
    }
  }

  def main(args: Array[String]): Unit = countdown(3)
}

有保证的优化

@tailrec 的关键优势是编译时保证。如果您的函数不具备栈安全性,您会立即得到提示,而不是等到大输入导致运行时崩溃后才发现问题。

尾递归 gcd

欧几里得算法本身就是尾递归的:递归调用就是函数体的完整结果。为它添加注解即可确认这一点。

import scala.annotation.tailrec

object Main {
  @tailrec
  def gcd(a: Int, b: Int): Int =
    if (b == 0) a else gcd(b, a % b)

  def main(args: Array[String]): Unit = {
    println(gcd(1071, 462))
  }
}

哪些情况会破坏尾部位置

以下常见模式会使调用离开尾部位置:

  • 对结果执行算术运算:n + f(...)。
  • 将结果包装在构造器中:x :: f(...)。
  • 在 try 代码块中使用结果。

非尾递归示例

这个求和函数不是尾递归的,因为加法包装了递归调用。为它添加 @tailrec 会导致编译错误。(示例未添加注解,因此可以运行。)

object Main {
  def sum(n: Int): Int =
    if (n == 0) 0
    else n + sum(n - 1)

  def main(args: Array[String]): Unit = {
    println(sum(100))
  }
}

为什么无法优化

因为 n + sum(n - 1) 必须记住 n,以便调用返回后完成加法,所以每一层都需要自己的栈帧。编译器无法将其压缩为循环,因此它不是尾递归。

大型尾递归循环

使用累加器的尾递归求和可以处理极大的输入而不发生溢出,因为它会重复使用一个栈帧。

import scala.annotation.tailrec

object Main {
  @tailrec
  def sumTo(n: Int, acc: Long = 0): Long =
    if (n == 0) acc else sumTo(n - 1, acc + n)

  def main(args: Array[String]): Unit = {
    println(sumTo(1000000))
  }
}

tailrec 要求 final 或局部方法

要使 @tailrec 生效,该方法不能被重写:它必须是 private、final,或局部/嵌套方法。开放方法可能被重写,从而破坏优化,因此编译器会拒绝它。

相互递归的注意事项

@tailrec 只会优化调用自身的函数。两个函数相互调用(相互递归)时,JVM 无法直接进行尾部优化;这种情况需要蹦床机制,稍后会介绍。

快速检查

检验您对 @tailrec 的理解。

回顾

您学习了@tailrec 注解:

  • 处于尾部位置的调用可以被优化为循环。
  • @tailrec 为栈安全性提供编译时保证。
  • 方法必须是 final、private 或局部方法。
  • 它只适用于自身递归,不适用于相互递归。

常见问题解答

「tailrec 注解」课时是免费的吗?

是的 — 「tailrec 注解」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Scala for Backend Engineering & Functional Programming 课程的其余内容,请升级到 CoddyKit PRO。 Scala for Backend Engineering & Functional Programming 课程共包含 4 节课。

「tailrec 注解」这节课中我会学到什么?

保证优化 你通过在浏览器中直接运行的动手代码来练习 Scala for Backend Engineering & Functional Programming,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Scala for Backend Engineering & Functional Programming 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Scala for Backend Engineering & Functional Programming 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「tailrec 注解」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Scala for Backend Engineering & Functional Programming 课中编写并运行代码吗?

能。每节 Scala for Backend Engineering & Functional Programming 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 递归基础
  2. tailrec 注解
  3. 累加器模式
  4. 蹦床调用
← 返回 Scala for Backend Engineering & Functional Programming