05. Kotlin 函数(基础篇)
函数是能完成某项特定任务的可复用代码块,是编程非常重要的一部分。事实上,程序基本上就是一系列函数的组合,用来完成较复杂的任务。
函数声明
Kotlin 中的函数很灵活,它可以独立于类或接口之外存在,不属于任何的类,即顶层函数(top-level function),也就是全局函数,之前接触的 main 函数就属于顶层函数;也可以存在于别的函数中,即局部函数;还可以存在于类或接口之中,即成员函数。
1 | fun 函数名(参数列表) : 返回值类型 { |
在参数列表后 “: 返回值类型” 指明函数的返回值类型,如果函数没有需要返回的数据,则 “: 返回值类型” 可以省略。对应地,如果函数有返回数据,就需要在函数体最后使用 return 语句将计算的数据返回。
举例:具有两个 Int 参数和 Int 返回类型的函数。
1 | fun sum(a: Int, b: Int): Int { |
不返回任何有意义值的函数。
函数体可以是表达式。 它的返回类型是推断的。
1 | fun sum(a: Int, b: Int) = a + b |
不返回有意义值的函数。
1 | fun printSum(a: Int, b: Int): Unit { |
Unit 返回类型可以省略。
1 | fun printSum(a: Int, b: Int) { |
函数参数
Kotlin 中的函数参数很灵活,具体体现在传递参数有多种形式上。
参数默认值
Default Parameter Values
在声明函数的时候可以为参数有默认值,这些值在跳过相应的参数时使用。这种写法可以极大减少重载函数。
写法:默认值通过向类型追加 = 来设置。
1 | // 函数定义 |
命名参数
Named Arguments
与大多数其他编程语言(Java、C++等)一样, Kotlin 支持根据方法和构造函数的定义顺序将参数传递给它们。 Kotlin还支持命名参数,以允许更清晰的调用并避免参数顺序错误。 这样的错误很难发现,因为编译器无法检测到它们,例如,当两个连续参数具有相同类型时。
1 | fun format(userName: String, domain: String) = "$userName@$domain" |
可变参数
Varargs 允许您
Kotlin 中函数的参数个数可以变化,它可以接受不确定数量的输入类型参数(这些参数具有相同的类型),有点像是传递一个数组。可以通过在参数名前面加 vararg 关键字的方式来表示这是可变参数(最佳实践中一般会定义为最后一个参数) 。
调用 vararg-function 时,可以单独传递参数,通过逗号分隔来传递任意数量的参数。如果您已经有一个数组,并希望将其内容传递给函数,请使用 spread 运算符(在数组前面加上 *):
1 | fun printAll(vararg messages: String) { // 1 |
- vararg 修饰符将参数转换为 vararg。
- 这允许使用任意数量的字符串参数调用 printAll。
- 由于有了命名参数,您甚至可以在 vararg 之后添加另一个相同类型的参数。这在 Java 中是不允许的,因为没有传递值的方法。
- 使用命名参数,您可以设置一个值,以便与 vararg 分开设置前缀。
- 在运行时,vararg 只是一个数组。要将其传递给 vararg 参数,请使用特殊的传播操作符 * ,它允许您传递
*entries
而不是 entries (一个Array<String>
)。
返回特殊数据
在函数体中可以通过 return 语句返回数据,返回数据类型要与函数声明的数据类型保持一致。本节讨论一些特殊的返回数据,其中包括:无返回数据和永远不会正常返回数据。
无返回数据与 Unit 类型
如果函数只是为了处理某个过程, 不需要返回具体数据,例如 println 函数。则其返回类型为 Unit。Unit 是只有一个值的类型 Unit。此值不必显式返回:
1 | fun printHello(name: String?): Unit { |
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 | val greetingFunction6 = { playerName: String, numBuildings: Int -> |
函数内联
lambda 可以让你更灵活地编写应用。然而,灵活也是要付出代价的。
在 JVM 上,你定义的 lambda 会以对象实例的形式存在。JVM 会为所有同 lambda 打交道的变量分配内存,这就产生了内存开销。更糟的是,lambda 的内存开销会带来严重的性能问题。显然,这种性能问题应当避免。
幸运的是,Kotlin 有一种优化机制叫内联,可以解决 lambda 引起的内存开销问题。有了内联,JVM 就不需要使用 lambda 对象实例了,因而避免了变量内存分配。
为了使用内联方法优化 lambda,以 inline 关键字标记使用 lambda 的函数即可。
函数引用
到目前为止,要把函数作为参数传给其他函数使用,我们都是先定义一个 lambda,然后把它作为参数传给另一个函数使用。除了传 lambda 表达式,Kotlin 还提供了其他方法:传递函数引用。函数引用可以把一个具名函数(使用 fun 关键字定义的函数)转换成一个值参。凡是使用lambda 表达式的地方,都可以使用函数引用。
要获得函数引用,使用 :: 操作符,后跟要引用的函数名。
函数引用在很多场景都很有用。例如,如果需要一个具名函数作为值参传给其他函数,采用 函数引用方法代替 lambda 就能达到同样的目的。或者在需要使用 Kotlin 标准库函数作为值参传 给其他函数使用时,也可以使用函数引用。
函数类型作为返回类型
和其他数据类型一样,函数类型也是有效的返回类型,也就是说,你可以定义一个能返回函数的函数。
1 | // 函数类型作为返回类型 |
能接受函数(以函数值参传入)或者返回函数的函数又叫高级函数。高级函数和 lambda 术语来自同一数学领域。高级函数广泛应用于函数式编程这种编程范式中。