05. Kotlin 函数(基础篇)

函数是能完成某项特定任务的可复用代码块,是编程非常重要的一部分。事实上,程序基本上就是一系列函数的组合,用来完成较复杂的任务。

函数声明

Kotlin 中的函数很灵活,它可以独立于类或接口之外存在,不属于任何的类,即顶层函数(top-level function),也就是全局函数,之前接触的 main 函数就属于顶层函数;也可以存在于别的函数中,即局部函数;还可以存在于类或接口之中,即成员函数。

1
2
3
4
fun 函数名(参数列表) : 返回值类型 {
函数体
return 返回值
}

在参数列表后 “: 返回值类型” 指明函数的返回值类型,如果函数没有需要返回的数据,则 “: 返回值类型” 可以省略。对应地,如果函数有返回数据,就需要在函数体最后使用 return 语句将计算的数据返回。

举例:具有两个 Int 参数和 Int 返回类型的函数。

1
2
3
fun sum(a: Int, b: Int): Int {
return a + b
}

不返回任何有意义值的函数。

函数体可以是表达式。 它的返回类型是推断的。

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

不返回有意义值的函数。

1
2
3
fun printSum(a: Int, b: Int): Unit {
println("sum of $a and $b is ${a + b}")
}

Unit 返回类型可以省略。

1
2
3
fun printSum(a: Int, b: Int) {
println("sum of $a and $b is ${a + b}")
}

函数参数

Kotlin 中的函数参数很灵活,具体体现在传递参数有多种形式上。

参数默认值

Default Parameter Values
在声明函数的时候可以为参数有默认值,这些值在跳过相应的参数时使用。这种写法可以极大减少重载函数。

写法:默认值通过向类型追加 = 来设置。

1
2
3
4
5
6
// 函数定义
fun printMessageWithPrefix(message: String, prefix: String = "Info") {
println("[$prefix] $message")
}
// 函数调用
printMessageWithPrefix("Hello")

命名参数

Named Arguments
与大多数其他编程语言(Java、C++等)一样, Kotlin 支持根据方法和构造函数的定义顺序将参数传递给它们。 Kotlin还支持命名参数,以允许更清晰的调用并避免参数顺序错误。 这样的错误很难发现,因为编译器无法检测到它们,例如,当两个连续参数具有相同类型时。

1
2
3
4
5
6
7
8
fun format(userName: String, domain: String) = "$userName@$domain"

fun main() {
// Calls a function with named arguments.
println(format(userName = "foo", domain = "bar.com"))
// 当使用命名参数调用函数时,可以按照任何喜欢的顺序指定它们。
println(format(domain = "frog.com", userName = "pepe"))
}

可变参数

Varargs 允许您

Kotlin 中函数的参数个数可以变化,它可以接受不确定数量的输入类型参数(这些参数具有相同的类型),有点像是传递一个数组。可以通过在参数名前面加 vararg 关键字的方式来表示这是可变参数(最佳实践中一般会定义为最后一个参数) 。

调用 vararg-function 时,可以单独传递参数,通过逗号分隔来传递任意数量的参数。如果您已经有一个数组,并希望将其内容传递给函数,请使用 spread 运算符(在数组前面加上 *):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun printAll(vararg messages: String) {   // 1
for (m in messages) println(m)
}
printAll("Hello", "Hallo", "Salut", "Hola", "你好") // 2

fun printAllWithPrefix(vararg messages: String, prefix: String) { // 3
for (m in messages) println(prefix + m)
}
printAllWithPrefix(
"Hello", "Hallo", "Salut", "Hola", "你好",
prefix = "Greeting: " // 4
)

fun log(vararg entries: String) {
printAll(*entries) // 5
}
log("Hello", "Hallo", "Salut", "Hola", "你好")
  1. vararg 修饰符将参数转换为 vararg。
  2. 这允许使用任意数量的字符串参数调用 printAll。
  3. 由于有了命名参数,您甚至可以在 vararg 之后添加另一个相同类型的参数。这在 Java 中是不允许的,因为没有传递值的方法。
  4. 使用命名参数,您可以设置一个值,以便与 vararg 分开设置前缀。
  5. 在运行时,vararg 只是一个数组。要将其传递给 vararg 参数,请使用特殊的传播操作符 * ,它允许您传递 *entries 而不是 entries (一个 Array<String>)。

返回特殊数据

在函数体中可以通过 return 语句返回数据,返回数据类型要与函数声明的数据类型保持一致。本节讨论一些特殊的返回数据,其中包括:无返回数据和永远不会正常返回数据。

无返回数据与 Unit 类型

如果函数只是为了处理某个过程, 不需要返回具体数据,例如 println 函数。则其返回类型为 Unit。Unit 是只有一个值的类型 Unit。此值不必显式返回:

1
2
3
4
5
6
7
fun printHello(name: String?): Unit {
if (name != null)
println("Hello $name")
else
println("Hi there!")
// `return Unit` or `return` is optional
}

Unit 返回类型声明也是可选的:

1
fun printHello(name: String?) { ... }

永远不会正常返回数据与 Nothing 类型

Kotlin 中提供一种特殊的数据类型 Nothing,Nothing 只用于函数返回类型声明,不能用于变量声明。Nothing声明的函数永远不会正常的返回,只会抛出异常。

single-expression function 单表达式函数

如果在函数体中表达式能够表示成为单个表达式时,那么返回类型、花括号、返回语句都可以省掉。表示方式更加简单。

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

局部函数

在本节之前声明的函数都是顶层函数,函数还可声明在类内部和另一个函数的内部,在类内部称为成员函数,在另一个函数内部称为局部函数。

局部函数可以访问所在外部函数中的变量。但是内部函数的作用域在外函数体内,因此直接访问局部函数会发生编译错误。

匿名函数

Kotlin 中可以使用匿名函数,匿名函数不需要函数名,需要 fun 关键字声明,还需要有参数列表和返回类型声明,函数体中需要包含必要的 return 语句。

函数类型定义包括两个部分,它们以箭头符号隔开:一对圆括号里面的函数参数和紧跟着的返回数据类型。

和具名函数一样,匿名函数可以不带参数,也可以带一个或多个任何类型的参数。需要带参数时,参数的类型放在匿名函数的类型定义中,参数名则放在函数定义中。

it 关键字

定义只有一个参数的匿名函数时,可以使用 it 关键字来表示参数名。

类型推断

Kotlin 的类型推断规则同样也适用函数类型,但为了帮助编译器更准确地推断变量类型,匿名函数的参数名和参数类型必须有。

1
2
3
4
5
val greetingFunction6 = { playerName: String, numBuildings: Int ->
val currentYear = 2025
println("Adding $numBuildings houses")
"Welcome to SimVillage, $playerName! (copyright $currentYear)"
}

函数内联

lambda 可以让你更灵活地编写应用。然而,灵活也是要付出代价的。

在 JVM 上,你定义的 lambda 会以对象实例的形式存在。JVM 会为所有同 lambda 打交道的变量分配内存,这就产生了内存开销。更糟的是,lambda 的内存开销会带来严重的性能问题。显然,这种性能问题应当避免。

幸运的是,Kotlin 有一种优化机制叫内联,可以解决 lambda 引起的内存开销问题。有了内联,JVM 就不需要使用 lambda 对象实例了,因而避免了变量内存分配。

为了使用内联方法优化 lambda,以 inline 关键字标记使用 lambda 的函数即可。

函数引用

到目前为止,要把函数作为参数传给其他函数使用,我们都是先定义一个 lambda,然后把它作为参数传给另一个函数使用。除了传 lambda 表达式,Kotlin 还提供了其他方法:传递函数引用。函数引用可以把一个具名函数(使用 fun 关键字定义的函数)转换成一个值参。凡是使用lambda 表达式的地方,都可以使用函数引用。

要获得函数引用,使用 :: 操作符,后跟要引用的函数名。

函数引用在很多场景都很有用。例如,如果需要一个具名函数作为值参传给其他函数,采用 函数引用方法代替 lambda 就能达到同样的目的。或者在需要使用 Kotlin 标准库函数作为值参传 给其他函数使用时,也可以使用函数引用。

函数类型作为返回类型

和其他数据类型一样,函数类型也是有效的返回类型,也就是说,你可以定义一个能返回函数的函数。

1
2
3
4
5
6
7
8
9
10
11
// 函数类型作为返回类型
fun configureGreetingFunction(): (String) -> String {
val structureType = "hospitals"
var numBuildings = 5
return { playerName: String ->
val currentYear = 2018
numBuildings++
println("Adding $numBuildings $structureType")
"Welcome to SimVillage, $playerName! (copyright $currentYear)"
}
}

能接受函数(以函数值参传入)或者返回函数的函数又叫高级函数。高级函数和 lambda 术语来自同一数学领域。高级函数广泛应用于函数式编程这种编程范式中。