函数
Kotlin官网:Functions and Lambdas-Functions
函数声明
使用fun
关键字声明函数
fun double(x: Int): Int {
return 2 * x
}
函数调用
普通的调用方式:
val result = double(2)
.调用:
Sample().foo() // 创建Sample类实例,调用foo函数
参数
函数参数的定义格式为参数名: 类型。
参数名和类型之间用冒号分隔,多个参数之间用逗号分割,参数必须指定类型:
fun powerOf(number: Int, exponent: Int) {
...
}
默认参数
函数的参数可以有默认值,在调用函数传参被省略时使用。
参数默认值可以减少声明重载函数。
声明的格式为参数声明=默认值
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) {
...
}
重写带默认值参数的函数时,会默认使用被重写函数的默认值,重写函数必须省略默认值声明:
open class A {
open fun foo(i: Int = 10) { ... }
}
class B : A() {
override fun foo(i: Int) { ... } //不允许声明默认值
}
如果带默认值的参数是在其他参数前面,想通过省略参数来使用默认值,需要通过命名参数指定:
fun foo(bar: Int = 0, baz: Int) { /* ... */ }
foo(baz = 1) // bar使用默认值0
函数最后参数为lambda,其他参数有默认值,在括号外声明lambda可以不给函数传参
fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /* ... */ }
foo(1) { println("hello") } // bar传1,baz使用默认值1
foo { println("hello") } // bar使用默认值0,baz使用默认值1
命名参数
调用函数时可以通过参数名指定传参,这样可以在多个参数有默认值时指定给哪个参数传参,也可以用来增加可读性。
举个例子:
有下面一个带多个参数和默认值的函数:
fun reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
...
}
当只给第一个参数传参,其他参数使用默认值时,reformat(str)
没问题。当有多个参数需要传值时,reformat(str, true, true, false, '_')
,可读性非常差,如果写成下面的形式:
reformat(str,
normalizeCase = true,
upperCaseFirstLetter = true,
divideByCamelHumps = false,
wordSeparator = '_'
)
这样可以使代码更易读。
关于使用函数声明顺序传参和使用参数名字传参混用,如下函数:
reformat(str, wordSeparator = '_')
当同时使用参数顺序和参数名字传参时,所有使用顺序的参数必须写在名字传参的前面,例如一个f(x,y)函数,f(1, y = 2)
这样写是可以的,1使用0位置给x传参,y=2给名为y的参数传参,而f(x = 1, 2)
这样写是不允许的。
对于vararg声明的可变参数,可以使用展开运算符的方式给命名参数传参
fun foo(vararg strings: String) { /* ... */ }
foo(strings = *arrayOf("a", "b", "c"))
返回Unit
对于没有返回值,类似于Java中void的函数,Kotlin中的返回值是Unit。
返回Unit类型无需显式声明return,函数返回值类型也可以省略。
fun printHello(name: String?): Unit {
if (name != null)
println("Hello ${name}")
else
println("Hi there!")
// `return Unit`或`return`可以省略
}
//省略函数返回值类型声明
fun printHello(name: String?) {
...
}
单表达式函数
单表达式函数可以省略大括号,直接写成=赋值的形式。返回值类型在可以被编译器推导出时可以省略。
fun double(x: Int): Int = x * 2
//Int型返回值编译器可以通过x*2推导出,此时可以省略
fun double(x: Int) = x * 2
声明返回值类型
对于有函数体的函数,除了Unit类型返回值可以被省略,其他类型都要显式声明,因为函数内部可能非常复杂,编译器无法推导出返回值类型。
可变参数
使用vararg
声明,和java中的可变参数类似:
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts is an Array
result.add(t)
return result
}
参数的数量可变,调用:
val list = asList(1, 2, 3)
对于使用者来说,可变参数可以被视为T类型的数组,上例中,ts的类型为Array<out T>
函数参数中只能有一个可变参数。
如果可变参数不是函数的最后一个参数,此时有两种传参方式,一种是可以通过命名参数传参,另一种是,如果可变参数后是一个函数,可以写成括号外lambda的形式。
给可变参数传递的参数也有两种形式,一种是一个一个的传,另一种是通过展开运算符(*数组),这样可以将已有数组的内容传递给可变参数:
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
中缀声明
声明为中缀有以下要求:
- 成员函数或扩展函数;
- 一个参数
- 使用infix关键字标记
// 为Int添加扩展函数
infix fun Int.shl(x: Int): Int {
...
}
//使用中缀声明的扩展函数
1 shl 2
// 等价于
1.shl(2)
作用域
Kotlin中,函数可以直接声明在文件中,可以直接调用,无需包含在类中。也可以声明为一级成员,局部函数,成员函数和扩展函数。
局部函数
函数可以在局部声明,例如函数内:
fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: Set<Vertex>) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}
局部函数可以访问外部函数作用域内的局部变量。例如上例可以写成:
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}
成员函数
定义在类或对象中的函数:
class Sample() {
fun foo() { print("Foo") }
}
调用:
Sample().foo() //先创建Sample类的实例,再调用foo函数
类和继承的知识详见3-1类和继承。
泛型函数
函数参数类型可以是泛型,泛型在函数名前用尖括号定义:
fun <T> singletonList(item: T): List<T> {
// ...
}
关于泛型的详细说明见3-1泛型
内联函数
详见4-3内联函数
扩展函数
详见3-5扩展
高阶函数和lambda
详见4-2lambda
尾递归
Kotlin支持尾递归,可以用递归的写法替代一些循环,没有栈溢出的风险。
当函数使用railrec
修饰,编译器会将递归转换成循环,更快更高效:
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
上例中的函数作用是寻找自身cos值不再变化的数,算法只是不断的cos自己并与自己比较,结果为0.7390851332151607。上例等价于这样写:
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if (x == y) return x
x = y
}
}
要使用tailrec
修饰函数有如下要求
- 函数内必须在最后调用自己
- 调用自己后不能再执行其他内容
- 不能在try/catch/finally中使用
- 目前仅支持JVM