Skip to content

函数声明与参数

源:Kotlin Functions

函数是 Kotlin 中的一等公民。Kotlin 极大地增强了函数声明的灵活性,通过默认参数、具名参数等特性,解决了 Java 中常见的重载爆炸和可读性问题。

函数基础声明

Kotlin 使用 fun 关键字声明函数。

kotlin
fun sum(a: Int, b: Int): Int {
    return a + b
}

单表达式函数

如果函数只包含一个表达式,可以省略大括号并使用等号 = 指定主体,同时编译器可以推断返回类型。

kotlin
fun sum(a: Int, b: Int) = a + b

参数的艺术

默认参数 (Default Arguments)

Kotlin 允许为函数参数指定默认值。这极大地减少了重载函数的数量。

kotlin
fun display(message: String, prefix: String = "INFO", importance: Int = 1) {
    println("[$prefix] $message (Level: $importance)")
}

// 调用
display("Hello") // 使用所有默认值
display("Error", "ERR") // 覆盖部分默认值

JVM 互操作性

在 Java 中调用带默认参数的 Kotlin 函数时,默认需要传入所有参数。如果希望 Java 也能看到多个重载版本,需添加 @JvmOverloads 注解。

具名参数 (Named Arguments)

调用函数时,可以根据参数名显式指定值。这不仅提升了可读性,还允许在不破坏逻辑的前提下改变传参顺序。

kotlin
display(importance = 5, message = "System Crash") // 清晰且灵活

可变参数 (Varargs)

使用 vararg 修饰符允许函数接受任意数量的参数。

kotlin
fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) result.add(t)
    return result
}

伸展操作符 (Spread Operator)

如果你已经有一个数组并想将其内容传递给 vararg 参数,使用 * 前缀。

kotlin
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4) // 结果:[-1, 0, 1, 2, 3, 4]

顶层函数与作用域

在 Kotlin 中,函数可以直接定义在 .kt 文件的顶层,无需包裹在类中。

底层实现

编译器会将顶层函数转换为 JVM 静态方法。例如,在 Utils.kt 中定义的顶层函数,在 Java 中可以通过 UtilsKt.functionName() 调用。

局部函数 (Local Functions)

Kotlin 支持在函数内部定义函数。这有助于封装那些仅在特定逻辑块内使用的辅助代码,同时局部函数可以访问外层函数的局部变量(闭包)。

kotlin
fun dfs(graph: Graph) {
    val visited = HashSet<Node>()
    fun visit(node: Node) {
        if (visited.add(node)) {
            node.neighbors.forEach(::visit)
        }
    }
    visit(graph.root)
}

函数类型与成员引用

函数可以作为参数传递,也可以赋值给变量。使用 :: 可以获取函数引用。

kotlin
val operation: (Int, Int) -> Int = ::sum
val result = operation(1, 2)

尾递归优化 (Tail Recursion)

Kotlin 支持一种称为尾递归(Tail Recursion)的函数式编程风格。当一个函数的最后一个操作是调用其自身时,编译器可以将递归优化为高效的循环,从而避免栈溢出(Stack Overflow)风险并提升性能。

使用 tailrec 关键字

要启用此优化,需使用 tailrec 修饰符标记函数。

kotlin
// 递归调用是计算的最后一步
tailrec fun factorial(n: Long, accum: Long = 1): Long {
    val soFar = n * accum
    if (n <= 1) return soFar
    // 尾调用:直接返回递归结果,没有后续运算
    return factorial(n - 1, soFar)
}
kotlin
// 错误:JVM 无法优化,深递归会导致 StackOverflow
fun factorialOld(n: Long): Long {
    if (n <= 1) return 1
    // 递归返回后还需要执行乘法,不是尾调用
    return n * factorialOld(n - 1)
}

优化条件与限制

要让 tailrec 生效,函数必须满足严格条件:

  1. 必须调用自身:递归调用指向当前函数。
  2. 必须是最后一步:递归调用后不能有任何代码(包括算术运算)。
  3. 环境限制:不能在 try/catch/finally 块中进行递归调用。
底层原理:循环展开

tailrec 并不是黑科技,编译器只是将其重写为 while 循环。

上述 factorial 函数编译后的伪代码(Java 视角):

java
public static final long factorial(long n, long accum) {
    while (true) {
        long soFar = n * accum;
        if (n <= 1) return soFar;
        // 更新参数并循环,而非入栈
        n = n - 1;
        accum = soFar;
    }
}

这保证了无论 n 有多大,占用的栈空间始终是常数级(几百字节)。

总结

  • 终结重载地狱:利用默认参数和具名参数简化 API 设计。
  • 声明式风格:单表达式函数让代码更像数学定义。
  • 封装极致:局部函数有效控制了代码的污染范围。
  • 顶层自由:不再为放一个简单的工具函数而创建繁琐的 Utils 类。