☰
Kotlin与Java虚拟机四:函数
2026/11/6 5:22:50 网站建设 项目流程

第4章 函数

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

4.1 基本声明

Kotlin 用fun关键字声明函数:

// 完整写法:fun 函数名(参数: 类型): 返回类型 { 体 }funmax(a:Int,b:Int):Int{returnif(a>b)aelseb}// 函数体只有一个表达式时,可以省略花括号和 returnfunmin(a:Int,b:Int):Int=if(a<b)aelseb// 进一步:返回类型可推断时(表达式函数),可以省略返回类型funadd(a:Int,b:Int)=a+b// 无返回值(相当于 Java 的 void)fungreet(name:String){println("你好,$name")}

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

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

4.2.1 默认参数

// 最后一个参数有默认值fungreet(name:String,prefix:String="先生/女士"){println("$prefix$name")}greet("张三")// 先生/女士 张三greet("李四","教授")// 教授 李四

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

4.2.2 命名参数

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

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

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

4.2.3 可变参数 vararg

funsum(varargnumbers:Int):Int{vartotal=0for(ninnumbers)total+=nreturntotal}sum(1,2,3)// 6sum(1,2,3,4,5)// 15// 已有的数组展开传参,用 * 前缀valarr=intArrayOf(1,2,3)sum(*arr)// 6

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

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

4.3.1 顶层函数

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

// utils.kt —— 不需要包一层类funparseIntOrNull(s:String):Int?=s.toIntOrNull()funmain(){println(parseIntOrNull("123"))// 123println(parseIntOrNull("abc"))// null}

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

4.3.2 局部函数

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

funprocessUser(name:String,age:Int){// 局部函数:只在 processUser 内部可见funvalidate():Boolean{returnname.isNotBlank()&&age>0}if(validate()){println("处理$name,$age岁")}else{println("参数非法")}}

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

4.3.2 局部函数闭包

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

funmakePrinter(prefix:String):()->Unit{varcount=0// 局部函数:每次调用都让 count 自增并打印funprintLine(msg:String){count++println("$prefix#$count:$msg")}return{printLine("hello")}}valp=makePrinter("LOG")p();p();p()// LOG #1: hello// LOG #2: hello// LOG #3: hello

4.3.3 成员函数

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

classCalculator{funadd(a:Int,b:Int)=a+b// 成员函数}Calculator().add(1,2)

4.4 表达式函数与Unit

// 这个函数没有有意义的返回值funlog(msg:String):Unit{println("[LOG]$msg")}// 写不写 : Unit 都行,编译器会推断funlog2(msg:String){println("[LOG]$msg")}

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

4.5 尾递归(tailrec)

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

// 普通递归,n 大了会栈溢出funfactorial(n:Int):Int=if(n<=1)1elsen*factorial(n-1)// 尾递归优化版本:把累积器 acc 作为参数传进去tailrecfunfactorialTail(n:Int,acc:Int=1):Int{if(n<=1)returnaccreturnfactorialTail(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 项(提示:需要两个累积参数)。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询