函数声明与参数
函数是 Kotlin 中的一等公民。Kotlin 极大地增强了函数声明的灵活性,通过默认参数、具名参数等特性,解决了 Java 中常见的重载爆炸和可读性问题。
函数基础声明
Kotlin 使用 fun 关键字声明函数。
fun sum(a: Int, b: Int): Int {
return a + b
}单表达式函数
如果函数只包含一个表达式,可以省略大括号并使用等号 = 指定主体,同时编译器可以推断返回类型。
fun sum(a: Int, b: Int) = a + b参数的艺术
默认参数 (Default Arguments)
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)
调用函数时,可以根据参数名显式指定值。这不仅提升了可读性,还允许在不破坏逻辑的前提下改变传参顺序。
display(importance = 5, message = "System Crash") // 清晰且灵活可变参数 (Varargs)
使用 vararg 修饰符允许函数接受任意数量的参数。
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) result.add(t)
return result
}伸展操作符 (Spread Operator)
如果你已经有一个数组并想将其内容传递给 vararg 参数,使用 * 前缀。
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 支持在函数内部定义函数。这有助于封装那些仅在特定逻辑块内使用的辅助代码,同时局部函数可以访问外层函数的局部变量(闭包)。
fun dfs(graph: Graph) {
val visited = HashSet<Node>()
fun visit(node: Node) {
if (visited.add(node)) {
node.neighbors.forEach(::visit)
}
}
visit(graph.root)
}函数类型与成员引用
函数可以作为参数传递,也可以赋值给变量。使用 :: 可以获取函数引用。
val operation: (Int, Int) -> Int = ::sum
val result = operation(1, 2)尾递归优化 (Tail Recursion)
Kotlin 支持一种称为尾递归(Tail Recursion)的函数式编程风格。当一个函数的最后一个操作是调用其自身时,编译器可以将递归优化为高效的循环,从而避免栈溢出(Stack Overflow)风险并提升性能。
使用 tailrec 关键字
要启用此优化,需使用 tailrec 修饰符标记函数。
// 递归调用是计算的最后一步
tailrec fun factorial(n: Long, accum: Long = 1): Long {
val soFar = n * accum
if (n <= 1) return soFar
// 尾调用:直接返回递归结果,没有后续运算
return factorial(n - 1, soFar)
}// 错误:JVM 无法优化,深递归会导致 StackOverflow
fun factorialOld(n: Long): Long {
if (n <= 1) return 1
// 递归返回后还需要执行乘法,不是尾调用
return n * factorialOld(n - 1)
}优化条件与限制
要让 tailrec 生效,函数必须满足严格条件:
- 必须调用自身:递归调用指向当前函数。
- 必须是最后一步:递归调用后不能有任何代码(包括算术运算)。
- 环境限制:不能在
try/catch/finally块中进行递归调用。
底层原理:循环展开
tailrec 并不是黑科技,编译器只是将其重写为 while 循环。
上述 factorial 函数编译后的伪代码(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类。