Kotlin 函数

函数是组织代码的最基本单元。Kotlin 的函数语法吸收了现代语言的精华——默认参数命名参数表达式函数体扩展函数尾递归优化——很多 Java 里要写一堆重载的功能,Kotlin 一个函数搞定。

1. 基本函数声明

函数用 fun 关键字声明,参数列表写在名字后面,返回类型用冒号分隔:

// 基本函数:fun + 名字 + 参数 + 返回类型 + body
fun add(a: Int, b: Int): Int {
    return a + b
}

// 返回类型可省略(编译器推断,但公共函数建议显式)
fun sum(a: Int, b: Int) = a + b

// 无返回值:Unit(可省略)
fun printHi(name: String): Unit {
    println("hi, $name")
}

// 等价于
fun printHi2(name: String) {
    println("hi, $name")
}

注意 Kotlin 没有 Java 的 public static 等修饰符——函数可以写在文件顶层,不需要包在 class 里。访问修饰符(privateinternalpublic)是可选的,默认 public。

2. 表达式函数体(简洁语法)

对于一行能写完的简单函数,可以用 = 直接写返回值,省略花括号和 return:

// 表达式函数体:一行搞定
fun double(n: Int) = n * 2
fun isAdult(age: Int) = age >= 18
fun greet(name: String) = "你好, $name!"

// 等价的块函数体
fun double2(n: Int): Int {
    return n * 2
}

// 表达式函数体用于"小函数",块函数体用于"复杂逻辑"
// 一行的简单计算/返回,优先用表达式体

表达式函数体让代码极简洁——fun double(n) = n * 2 比 Java 的 int double(int n) { return n * 2; } 短一半。返回类型也可以省略,但公共 API 建议显式写出(更稳定)。

3. 默认参数 + 命名参数(Kotlin 杀手锏)

这是 Kotlin 解决 Java"重载爆炸"的方案——一个函数直接搞定:

// 默认参数:解决 Java 的"重载爆炸"问题
fun greet(name: String, greeting: String = "你好", suffix: String = "!"): String {
    return "$greeting, $name$suffix"
}

println(greet("小明"))
// 你好, 小明!

println(greet("小明", "嗨"))
// 嗨, 小明!

println(greet("小明", "嗨", "!!!"))
// 嗨, 小明!!!

// 命名参数:跳过中间的默认参数
println(greet(name = "小明", suffix = "?"))
// 你好, 小明?

// Java 没有 default,只能写多个重载:
// greet(name)
// greet(name, greeting)
// greet(name, greeting, suffix)
// Kotlin 一个函数搞定!

Java 里同一个方法要支持各种参数组合,得写一堆重载(短代码到处都是 greet(name)greet(name, greeting)greet(name, greeting, suffix))。Kotlin 一个函数声明就搞定,而且调用时用命名参数可以跳过中间的默认值,非常灵活。

命名参数的另一个好处:可读性greet(name = "小明", greeting = "嗨") 一眼就懂,不用看函数签名。

4. 可变参数(vararg)

参数数量任意时用 vararg:

// 可变参数(vararg):参数数量任意
fun sum(vararg nums: Int): Int {
    return nums.sum()
}

println(sum(1, 2, 3))           // 6
println(sum())                  // 0
println(sum(1, 2, 3, 4, 5))    // 15

// vararg 参数在函数内是 Array<out T>
// 遍历
fun printAll(vararg items: String) {
    for (item in items) println(item)
}

// 把已有数组展开传给 vararg:用 * 展开(spread operator)
val existing = arrayOf(1, 2, 3)
val total = sum(*existing)       // 等价 sum(1, 2, 3)
println(total)                   // 6

// vararg 位置规则:通常是最后一个参数
fun format(prefix: String, vararg items: String) = "$prefix: ${items.joinToString()}"

vararg 参数在函数内是 Array<out T>。把已有数组展开传给 vararg 用 *(spread operator),类似 Python 的 *list、JS 的 ...arr

5. infix 中缀调用

infix 修饰符的函数可以省略点和括号,写起来更像自然语言:

// infix:中缀调用,省略点和括号
infix fun Int.pow(exp: Int): Int {
    var result = 1
    repeat(exp) { result *= this }
    return result
}

println(2 pow 10)               // 1024 (而不是 2.pow(10))
println(10 to "十")             // Pair(10, "十"),标准库的 infix to

// infix 的要求:
// - 必须是成员函数或扩展函数
// - 必须只有一个参数
// - 参数不能是 vararg 或有默认值

// 标准库的 infix 示例:
println(1 in 1..5)             // in 是关键字,不是 infix 函数
println(5 downTo 1)            // downTo 是 infix
println(1 until 10 step 2)     // until 和 step 都是 infix

infix 函数的要求:必须是成员函数或扩展函数,且只有一个参数(不能 vararg,不能有默认值)。

标准库大量用 infix:1 to "one"(创建 Pair)、5 downTo 1(递减区间)、list zip list2(拉链配对)、expr and 0xFF(位运算)。

6. 扩展函数(Kotlin 王牌特性)

扩展函数让你不修改原类源码、不继承的情况下,给已有类(包括 String、Int、第三方库的类)添加方法:

// 扩展函数:给已有类"添加"方法,不改它的源码
// 这是 Kotlin 最实用的特性之一
fun String.shout() = this.uppercase() + "!!!"
println("hello".shout())        // HELLO!!!

fun Int.isEven() = this % 2 == 0
println(4.isEven())             // true

fun List<Int>.sumEven() = this.filter { it % 2 == 0 }.sum()
println(listOf(1, 2, 3, 4).sumEven())  // 6

// 扩展属性
val String.firstChar: Char
    get() = this[0]
println("Kotlin".firstChar)     // K

// 扩展是【静态解析】的,不是真正的虚函数
// 也就是说调用哪个扩展,看【声明类型】而不是运行时类型
// 但实际使用中这个区别很少出问题

这是 Kotlin 实用主义的极致体现。标准库大量用扩展:"5".toIntOrNull()list.firstOrNull()file.readText()"x".repeat(3) 本质都是扩展函数。

原理:扩展是语法糖,本质是静态函数。所以扩展是静态解析的——调用哪个扩展看声明类型而非运行时类型(不是真正的虚函数)。但实际使用中这个区别很少出问题。

7. 局部函数与闭包

函数内部可以再定义函数,用于封装局部逻辑避免重复:

// 局部函数:在函数内部定义的函数(嵌套)
fun process(data: List<Int>): Int {
    // 局部函数:封装一段逻辑,只在外层可见
    fun validate(n: Int): Boolean {
        return n in 0..100
    }

    fun normalize(n: Int): Int {
        return if (n < 0) 0 else if (n > 100) 100 else n
    }

    return data
        .filter { validate(it) }
        .sumOf { normalize(it) }
}

// 局部函数可以访问外层函数的局部变量(闭包)
fun counter(): () -> Int {
    var count = 0
    return {
        count++
        count
    }
}

val c = counter()
println(c())    // 1
println(c())    // 2
println(c())    // 3

局部函数可以访问外层函数的局部变量(闭包)。第二个例子展示了用闭包实现 counter 函数式工厂——返回的 lambda 捕获了 count 变量,每次调用都加 1。

8. 尾递归(tailrec)

递归写法优雅,但容易栈溢出。Kotlin 用 tailrec 关键字让编译器自动把真正的尾递归优化为循环:

// 尾递归:用 tailrec 关键字,编译器优化为循环,避免栈溢出
// 阶乘的尾递归版本
tailrec fun factorial(n: Long, acc: Long = 1): Long =
    if (n <= 1) acc else factorial(n - 1, acc * n)

println(factorial(5))           // 120
println(factorial(20))          // 2432902008176640000

// 没加 tailrec,大 n 会栈溢出
// factorial(100000)            // 加了 tailrec 就不会

// 条件:函数必须以【自身调用】作为最后一步(真正的尾调用)
// 否则编译器报错

条件:函数必须以自身调用作为最后一步。如果不是真尾递归(如 return n * factorial(n-1),乘法在调用之后),编译器会报错提示。tailrec 让你用递归思维写代码,运行时性能等同循环

9. 高阶函数预览

Kotlin 函数可以接收函数作为参数返回函数,这叫高阶函数。完整讲解见 Lambda 与高阶函数章节,这里先看个例子:

// 函数类型:(Int, Int) -> Int
fun calc(a: Int, b: Int, op: (Int, Int) -> Int): Int {
    return op(a, b)
}

// 用 lambda 传函数
val result1 = calc(10, 3) { x, y -> x + y }      // 13
val result2 = calc(10, 3) { x, y -> x * y }      // 30
val result3 = calc(10, 3) { x, y -> x - y }      // 7

// 函数引用(::)
fun add(a: Int, b: Int) = a + b
val sum1 = calc(10, 3, ::add)                     // 13

小结

这一章你掌握了 Kotlin 函数的所有姿势:基本声明、表达式函数体、默认参数、命名参数、vararg、infix、扩展函数、局部函数、tailrec。最大的甜点是默认 + 命名参数(消灭重载)和扩展函数(给别人的类加方法)。下一篇看 Kotlin 的空安全——这是 Kotlin 区别于 Java 的最大改进。

← 上一篇 Kotlin 控制流

下一篇 Kotlin 空安全

✈️💬