Scala 中的递归函数是指在函数体内调用自身的函数,是函数式编程中处理循环逻辑的常用方式。以下是关于 Scala 递归函数的核心知识:
1. 基本定义与语法
递归函数的定义与普通函数类似,但需在函数体内调用自身,且必须明确指定返回值类型(Scala 编译器无法自动推断递归函数的返回类型)。
语法示例:
scala
// 计算 n 的阶乘(n! = n × (n-1) × ... × 1)
def factorial(n: Int): Int = {
if (n <= 1) 1 // 终止条件:递归的"出口"
else n * factorial(n - 1) // 递归调用:自身调用,参数逐步逼近终止条件
}
// 调用
println(factorial(5)) // 输出:120
2. 核心要素
递归函数必须满足两个条件,否则会导致无限递归(栈溢出):
- 终止条件:存在一个或多个 base case,当满足条件时停止递归(如上述
n <= 1时返回 1)。 - 递归步骤:函数每次调用时,参数需逐步逼近终止条件(如
n每次减 1)。
3. 尾递归(Tail Recursion)
普通递归可能因调用栈过深导致 StackOverflowError,而尾递归可避免此问题。尾递归指递归调用是函数体的最后一个操作(即递归调用的结果直接作为函数返回值,无需额外计算)。Scala 编译器会对尾递归进行优化,将其转换为循环,避免栈溢出。
示例:尾递归实现阶乘
scala
// 尾递归需要一个辅助函数保存中间结果
def factorialTail(n: Int): Int = {
// 辅助函数:acc 用于累计结果
def loop(current: Int, acc: Int): Int = {
if (current <= 1) acc // 终止条件:返回累计结果
else loop(current - 1, current * acc) // 尾递归调用:最后一步直接返回递归结果
}
loop(n, 1) // 初始调用:累计值从 1 开始
}
println(factorialTail(10000)) // 不会栈溢出(普通递归此处会报错)
4. 尾递归检查
可通过 @annotation.tailrec 注解验证函数是否为尾递归,若不是则编译报错。
scala
import scala.annotation.tailrec
def sumTail(n: Int): Int = {
@tailrec // 验证是否为尾递归
def loop(current: Int, acc: Int): Int = {
if (current == 0) acc
else loop(current - 1, current + acc) // 尾递归:正确
}
loop(n, 0)
}
若函数不是尾递归(如 else n + sum(n-1)),添加 @tailrec 会编译失败。
5. 常见递归场景
- 数学计算:阶乘、斐波那契数列(需优化为尾递归避免低效)、求和 / 乘积等。
- 集合处理:遍历列表、树形结构(如二叉树的深度计算、遍历)。
示例:递归遍历列表求和
scala
def sumList(list: List[Int]): Int = list match {
case Nil => 0 // 终止条件:空列表返回 0
case head :: tail => head + sumList(tail) // 递归:头元素 + 剩余列表的和
}
println(sumList(List(1, 2, 3, 4))) // 输出:10
6. 注意事项
- 栈溢出风险:非尾递归函数处理大数据时易栈溢出,优先使用尾递归或循环。
- 性能:尾递归性能接近循环,普通递归因栈操作可能较慢。
- 可读性:递归适合表达分治、树形结构等逻辑,复杂循环可用递归简化代码。
掌握递归(尤其是尾递归)是 Scala 函数式编程的基础,合理使用可写出简洁且高效的代码。