Kotlin学习(七):函数
函数基本用法
Kotlin
函数必须用 fun
关键字开头,后面紧跟着函数名,以及一对小括号,小括号中是函数参数列表,如果函数有返回值,在小括号后面加冒号 (:)
,冒号后面是函数的返回值类型。
fun double(x: Int): Int {
return 2 * x
}
调用函数使用传统方法:
val result = double(2)
调用成员函数使用点表示法:
Stream().read() // 创建类 Stream 实例并调用 read()
使用中缀标记法调用函数
Kotlin
允许使用中缀表达式调用函数。所谓中缀表达式,就是指将函数名称放到两个操作数中间。这两个操作数,左侧是包含函数的对象或值,右侧是函数的参数值。从这个描述可以看出,并不是所有的函数都支持中缀表达式。支持中缀标记法调用的函数必须满足下面3个条件。
- 成员函数或者扩展函数。
- 只有一个参数。
- 使用
infix
关键字声明函数。
下面举一个例子,在这个例子中,使用扩展给 String 类添加一个除法操作。什么是字符串的除法操作呢?这里的字符串的除法实际上也是一个语法糖,就是去除分子字符串中包含的所有分母字符串
infix fun String.div(str: String): String {
return this.replace(str, "")
}
如果按照一般的方式调用,那么可以通过下面的方法调用
fun main(args: Array<String>) {
var str = "Hello world"
println(str.div("l")) // 输出:Heo word
}
如果使用中缀表达式调用,那么写法如下:
fun main(args: Array<String>) {
var str = "Hello world"
println(str div "l") // 输出:Heo word
}
同时,中缀表达式还可以连续使用
fun main(args: Array<String>) {
var str = "Hello world"
println(str div "l") //输出:Heo word
println(str div "l" div "o") //输出:He wrd
}
**注:**中缀函数调用的优先级低于算术操作符、类型转换以及 rangeTo
操作符。 以下表达式是等价的:
1 shl 2 + 3
等价于1 shl (2 + 3)
0 until n * 2
等价于0 until (n * 2)
xs union ys as Set<*>
等价于xs union (ys as Set<*>)
另一方面,中缀函数调用的优先级高于布尔操作符 &&
与 ||
、is-
与 in-
检测以及其他一些操作符。这些表达式也是等价的:
a && b xor c
等价于a && (b xor c)
a xor b in c
等价于(a xor b) in c
完整的优先级层次结构请参见其语法参考。
单表达式函数
当函数返回单个表达式时,可以省略花括号并且在 = 符号之后指定代码体即可:
fun double(x: Int): Int = x * 2
当返回值类型可由编译器推断时,显式声明返回类型是可选的:
fun double(x: Int) = x * 2
函数参数和返回值
可变参数
函数的参数( 一般是最后一个参数)可以标记为 vararg
,这样会将该参数作为可变参数处理。所谓可变参数,就是指可以任意多个参数,在函数内部,会按数组来处理这些参数值。下面的 asList
函数是一个泛型函数,该函数只有一个参数,井且是可变参数。该函数返回 List<T>
类型。 asList
函数的功能是将一组值转换为List<T>
对象,并返回该对象。
fun <T> asList(vararg ts:T):List<T>{
var result=ArrayList<T>()
for (t in ts){
result.add(t)
}
return result
}
由于 ts
是可变参数,因此可以传递任意多个参数值,并且可以是任意类型的。
fun main(args: Array<String>) {
var result = asList(0, "hello", 1, 2, 3, 4, 5,"world")
println("result = [${
result}]")
}
在 asList
函数内部,类型为 vararg
参数会被看作一个 T
类型的数组,也就是说,asList
函数中的 ts
变量的类型为 Array<out T>
。
只有一个参数可以标记为 vararg
。如果 vararg
参数不是函数的最后一个参数,那么对于 vararg
参数之后的其他参数,可以使用命名参数语法来传递参数值,或者,如果参数类型是函数,可以在括号之外传递一个 Lambda
表达式。例如,下面的 asList
函数有3个参数,第1个参数是可变参数,后两个是 value1
和 value2
参数。由于最后一个参数不是可变参数,因此在传递 value1
和 value2
参数的值时需要使用命名参数。其中 Lambda
表达式会在后面详细介绍。
fun <T> asList(vararg ts: T, value1: Int, value2: String): List<T> {
var result = ArrayList<T>()
for (t in ts) {
result.add(t)
}
println("value1 = [${
value1}], value2 = [${
value2}], result = [${
result}]")
return result
}
fun main(args: Array<String>) {
println(product)
var result = asList(1, 2, 3, 4, 5, "world", value1 = 0, value2 = "hello")
println("result = [${
result}]") // 输出: value1 = [0], value2 = [hello], result = [[1, 2, 3, 4, 5, world]]
}
调用一个存在 vararg
参数的函数时,我们可以逐个传递参数值,如 asList(1,2,3)
,或者,如果我们已经有了一个数组,希望将它的内容传递给函数,可以使用展开 (spread)
操作符( 在数组之前加一个* ):
fun main(args: Array<String>) {
var a = arrayOf(0, "hello", "world", 1)
asList(-1, 3, *a, value1 = 87, value2 = "98") //输出:value1 = [87], value2 = [98], result = [[-1, 3, 0, hello, world, 1]]
}
返回值类型
如果函数体为多行语句组成的代码段,那么就必须明确指定返回值类型,除非这个函数返回 Unit
(不返回任何值),这时返回类型的声明可以省略。对于多行语句组成的函数, Kotlin
不会推断其返回值类型,因为这样的函数内部可能存在复杂的控制流,而且返回值类型对于代码的阅读者来说并不是那么一目了然(有些时候,甚至对于编译器来说也很难判定返回值类型)。
函数的作用域
在 Kotlin
中,函数可以定义在源代码的顶级范围内 (top level)
,这就意味着你不必像在 Java
、
C#
或 Scala
等语言中那样,创建一个类来容纳这个函数,除顶级函数外,Kotlin
中的函数也可以定义为局部函数、成员函数及扩展函数。
局部函数
Kotlin
支持局部函数,也就是嵌套在另一个函数内的函数。
fun saveFile() {
// 局部函数
fun getFullFileName(fn: String): String {
return "/Users/$fn"
}
var fileName = getFullFileName("test.txt")
println("$fileName 已经保存成功") // 输出:/Users/test.txt 已经保存成功
}
局部函数可以访问外部函数中的局部变量, 因此,在上面的例 中,fn
可以定义为一个局部变量。
fun saveFile() {
var fn = "test.txt"
// 局部函数
fun getFullFileName(): String {
return "/Users/$fn"
}
var fileName = getFullFileName()
println("$fileName 已经保存成功") // 输出:/Users/test.txt 已经保存成功
}
成员函数
成员函数是在类或对象内部定义的函数:
class Sample {
// 成员函数
fun foo() {
print("Foo")}
}
成员函数以点表示法调用:
Sample().foo() // 创建类 Sample 实例并调用 foo
泛型函数
函数可以有泛型参数,通过在函数名前使用尖括号指定:
fun <T> singletonList(item: T): List<T> { /*……*/ }
关于泛型函数的更多信息参见泛型。
内联函数
使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,并且会捕获一个闭包。 即那些在函数体内会访问到的变量。 内存分配(对于函数对象和类)和虚拟调用会引入运行时间开销。
但在很多情况下,通过将 Lambda 表达式内联在使用处,可以消除这些运行时消耗。要想让函数支持内联,需要在定义函数时使用 inline
关键字。
关于内联函数,可以查看这篇文章 。