Kotlin与Java虚拟机四:函数

第4章 函数

函数是 Kotlin 的一等公民。这一章讲函数怎么声明、参数怎么传、有哪些 Java 没有的写法。后面第 5 章讲 Lambda 和高阶函数,那是函数作为参数传递的玩法,本章先把函数本身讲清楚。

4.1 基本声明

Kotlin 用 fun 关键字声明函数:

kotlin 复制代码
// 完整写法:fun 函数名(参数: 类型): 返回类型 { 体 }
fun max(a: Int, b: Int): Int {
    return if (a > b) a else b
}

// 函数体只有一个表达式时,可以省略花括号和 return
fun min(a: Int, b: Int): Int = if (a < b) a else b

// 进一步:返回类型可推断时(表达式函数),可以省略返回类型
fun add(a: Int, b: Int) = a + b

// 无返回值(相当于 Java 的 void)
fun greet(name: String) {
    println("你好,$name")
}

注意:Unit 类型相当于 Java 的 void。如果函数不返回有意义的值,编译器自动返回 Unit,不用显式写。

4.2 参数:默认值、命名参数、可变参数

4.2.1 默认参数

kotlin 复制代码
// 最后一个参数有默认值
fun greet(name: String, prefix: String = "先生/女士") {
    println("$prefix $name")
}

greet("张三")              // 先生/女士 张三
greet("李四", "教授")      // 教授 李四

Java 没有参数默认值。Kotlin 这个特性减少了大量重载方法。

4.2.2 命名参数

调用函数时可以指定参数名:

kotlin 复制代码
greet(name = "王五", prefix = "医生")
// 顺序可以调换
greet(prefix = "警官", name = "赵六")

参数一多,命名参数能显著提升可读性。调用别人写的函数时,看到 greet("李四", "教授"),你不一定记得第二个参数是什么;写 greet(name = "李四", prefix = "教授") 就一目了然。

4.2.3 可变参数 vararg

kotlin 复制代码
fun sum(vararg numbers: Int): Int {
    var total = 0
    for (n in numbers) total += n
    return total
}

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

// 已有的数组展开传参,用 * 前缀
val arr = intArrayOf(1, 2, 3)
sum(*arr)              // 6

vararg 对应 Java 的 int... numbers,但 Kotlin 里可以有多个参数,vararg 不必放最后(放最后最常见)。

4.3 函数的位置:顶层、局部、成员

4.3.1 顶层函数

Java 要求所有方法写在类里。Kotlin 允许函数直接放在 .kt 文件顶层:

kotlin 复制代码
// utils.kt ------ 不需要包一层类
fun parseIntOrNull(s: String): Int? = s.toIntOrNull()

fun main() {
    println(parseIntOrNull("123"))    // 123
    println(parseIntOrNull("abc"))    // null
}

这个 parseIntOrNull 编译后会被放进 utils 文件对应的 UtilsKt 类里,作为 public static final 方法。Java 代码可以直接 UtilsKt.parseIntOrNull("123") 调用。

4.3.2 局部函数

函数里面还可以再定义函数:

kotlin 复制代码
fun processUser(name: String, age: Int) {
    // 局部函数:只在 processUser 内部可见
    fun validate(): Boolean {
        return name.isNotBlank() && age > 0
    }

    if (validate()) {
        println("处理 $name, $age 岁")
    } else {
        println("参数非法")
    }
}

局部函数可以访问外层函数的参数和局部变量,适合把一个大函数里的小步骤拆出来,又不想污染全局命名空间。

4.3.2 局部函数闭包

局部函数不仅能读外层参数,还能读写外层函数的局部变量,哪怕外层函数已经返回------这一点第 5 章讲 Lambda 闭包时还会展开。举个例子,下面这个函数返回一个计数器,内部状态就藏在局部函数里:

kotlin 复制代码
fun makePrinter(prefix: String): () -> Unit {
    var count = 0
    // 局部函数:每次调用都让 count 自增并打印
    fun printLine(msg: String) {
        count++
        println("$prefix #$count: $msg")
    }
    return { printLine("hello") }
}

val p = makePrinter("LOG")
p(); p(); p()
// LOG #1: hello
// LOG #2: hello
// LOG #3: hello

4.3.3 成员函数

写在类里面的函数,和 Java 一样:

kotlin 复制代码
class Calculator {
    fun add(a: Int, b: Int) = a + b    // 成员函数
}

Calculator().add(1, 2)

4.4 表达式函数与 Unit

kotlin 复制代码
// 这个函数没有有意义的返回值
fun log(msg: String): Unit {
    println("[LOG] $msg")
}

// 写不写 : Unit 都行,编译器会推断
fun log2(msg: String) {
    println("[LOG] $msg")
}

为什么要用 Unit 这个名字?因为 Kotlin 的函数类型系统里,"不返回值"也是一种类型 Unit(它只有一个单例对象),这样高阶函数才能统一处理所有函数。

4.5 尾递归(tailrec)

Kotlin 有一个 tailrec 修饰符,用于优化尾递归。写递归时,如果递归调用是函数的最后一个操作,编译器会把它优化成循环,避免栈溢出:

kotlin 复制代码
// 普通递归,n 大了会栈溢出
fun factorial(n: Int): Int =
    if (n <= 1) 1 else n * factorial(n - 1)

// 尾递归优化版本:把累积器 acc 作为参数传进去
tailrec fun factorialTail(n: Int, acc: Int = 1): Int {
    if (n <= 1) return acc
    return factorialTail(n - 1, n * acc)   // 最后一步是递归调用本身
}

println(factorialTail(10))    // 3628800

tailrec 要求递归调用必须是函数的最后一个操作。像上面 n * factorial(n-1) 这种,乘法在递归结果上做,就不是尾递归,不能加 tailrec。

4.6 本章小结

  • fun 声明函数;表达式函数 = expr 可省略花括号和返回类型。
  • 参数默认值、命名参数、vararg 可变参数,配合 * 展开数组。
  • 函数可以放在文件顶层(编译成静态方法)、函数内部(局部函数)、类里(成员函数)。
  • 无返回值对应 Unit。
  • tailrec 把符合条件的尾递归编译成循环,避免栈溢出。

动手练习

  1. 写一个函数 repeatStr(s: String, times: Int = 3, separator: String = " "),把 s 重复 times 次用分隔符连接,用命名参数调用三次,每次传不同的参数。
  2. 用局部函数把一个"计算工资"的函数拆成:基本工资、奖金、税三个内部步骤。
  3. 写一个尾递归函数计算斐波那契数列第 n 项(提示:需要两个累积参数)。

相关推荐
阡陌数智1 小时前
Claude API新版本重大变更与安全升级指南
java·linux·安全
一晌小贪欢1 小时前
Python基础:第 09 篇 · while 循环:重复直到不满足
开发语言·c++·python·数据可视化·面向对象·python办公
一条大祥脚2 小时前
CuTe 与 CuTe DSL:从 C++ 模板元编程到 Python DSL 的 GPU 编程演进
开发语言·c++·python·cutlass·cute·cute dsl
学长毕设3 小时前
基于SpringBoot的野生动物公益保护平台设计与实现-附源码
java·spring boot·后端
10年前端老司机3 小时前
Milvus 8种检索能力实践
数据库·python·agent
SL_staff3 小时前
离散制造排程破局:JVS-APS 四步闭环落地实践(开发者视角)
java·产品·全栈
这个昵称不容易3 小时前
SpringBoot 美容院管理系统:232个Java文件315个接口,5套角色从预约到发货一整条链路
java·springboot·毕设
Sakura_j_h_4 小时前
从零写一个 MCP stdio Server:用 Python 校验博客文章格式
开发语言·python·自动化·ai工具·mcp·json-rpc
大草原的小灰灰4 小时前
LLM应用的标准开发框架LangChain(八)- 中间件
python·中间件·langchain