函数声明
kotlin中的函数声明使用fun关键字
fun double(x: Int): Int{
return 2 * x
}
函数用法
通过传统途径调用方法:
var result = double(2)
参数
函数的参数通过Pascal符号定义,如name:type, 参数之间通过逗号隔开,每个参数必须指定的明确类型:
fun powerOf(number: Int, exponent: Int){ ... }
默认参数
函数的参数可以有默认值,这种情况发生在一个函数的参数在被调用是,本应传递的参数被省略掉。相比于其他的语言,这可以减少重载的数量。
fun read(b: Array<Byte>, off: 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被传递给括号外的函数调用,则允许传递缺省参数的值:
fun foo(bar: Int = 0, baz: Int = 1, qux: ()-> Unit){ ... }
foo(1) { println("hello") } //使用缺省参数 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(1, y=2)是允许的,但是f(x=1, 2)是不允许的。
可变数量的参数(vararg)可以通过使用扩展操作符在指定的表单中传递:
fun foo(vararg strings: String){ ... }
foo(strings = *arrayOf("a", "b", "c"))
注意在调用java函数的时候,命名参数语法是无法使用的,因为java字节码不会在意保护方法参数的名字。
空返回函数
如果一个函数没有返回有用的值,它的返回类型用Unit表示,Unit是一种只有一个值的类型-Unit,这个值不用显式返回。
fun printHello(name: String?):Unit{
if(name != null){
println("Hello ${name}")
}else{
println("Hi there!")
}
//return Unit 或者 return 是可选的
}
Unit 返回类型的声明是可选的,上面的代码等同于:
fun printHello(name: String){ ... }
单一表达式函数
当一个函数只返回一个表达式,花括号可以省略并且用一个“=”替换:
fun double(x: Int): Int = x * 2
当返回类型可以编译器推测出来的时候,返回类型也可以直接省略:
dun double(x: Int) = x * 2
明确的返回类型
块函数通常都必须指定返回类型,除非打算返回空(Unit, 这种情况是可选的),因为块函数本身的复杂的控制流程,kotlin没有为块函数做返回类型推测的处理,并且有时候返回类型对读者并非显而易见的(甚至于有时对编译器也是如此)。
变长参数
函数的参数被标记符 vararg 修饰:
fun<T> asList(vararg ts: T):List<T>{
val result = ArrayList<T>()
for(t in ts){ //ts 是一个数组
result.add(t)
}
return result
}
允许变长参数传递到函数中:
val list = asList(1,2,3)
在一个函数中,一个T类型的变长参数可以看成是T类型的一个数组,比如上面例子中的ts变量是类型Array<out T>数组
只有一个参数可以被标记为vararg, 如果被标记为vararg的参数不在参数列表中的最后一个,那么余下的参数可以通过命名参数语法传递,或者如果参数是一个函数类型,通过在括号外面传递一个lambda。
当我们调用一个变长参数函数,我们可以一个一个的传递参数,比如asList(1, 2, 3),或者加入我们已经有一个数组并且想把它的内容传递到函数中,我们可以使用扩展操作符(给数组加一个前缀‘*’):
val a= arrayOf(1,2,3)
val list = asList(-1, 0, *a, 4, 5)
中缀表示法
当函数被关键字infix修饰时,此函数可以使用中缀表示法调用(省略点号和圆括号),中缀函数必须满足下面条件:
- 它们必须是成员函数或者是扩展函数
- 它们必须只有一个参数
- 参数不允许是变长参数,并且不能有默认值
infix fun Int.shl(x: Int): Int{ ... }
//通过中缀表示法调用函数
1 shl 2
//等同于
1.shl(2)
中缀函数调用优先级低于算数运算符,类型转换和 **rangeTo** 操作符, 下面的表达式是等价的:
另一方面,中缀函数的调用优先级要高于布尔操作符&&和||, is-and, in-cheks和其他的操作符,下面的表达式是等价的:
- -- 1 shl 2 + 3 和 1 shl (2 + 3)
- -- 0 until n * 2 和 0 nutil (n * 2)
- -- xs union ys as Set<*> 和 xs union (ys as Set<*>)
- -- a && b xor c 和 a && (b xor c)
- -- a xor b in c 和 (a xor b) in c
注意中缀函数通常需要调用者和参数都是明确指定的,当你在当前的主体内使用中缀方法调用函数的时,你必须使用this指明;和普通的调用方法不一样,this不能省略,这样要求是为了确保明确的解析。
class MyStringCollection{
infix fun add(s: String){ ... }
fun build(){
this add "acc" //正确
add("acc") //正确
add "acc" //错误: 必须明确指出调用者
}
}
函数作用域
在Kotlin中,函数能够被生命在文件顶层,意味着你不用像java,C#或者Scala语言创建一个类去持有一个函数。除了顶级函数外,Kotlin函数也同样能是本地函数或者作为扩展函数。
本地函数
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())
}
局部函数能够访问函数外的变量,因此上面例子中,visited可以设置为一个局部变量
fun dfs(graph: Graph){
val visited = HashSet<Vertex>()
fun dfs(current: Vertex){
if(!visited.add(current) return
for(v in current.neighbors){
dfs(v, visited)
}
}
dfs(graph.vertices[0])
}
成员函数
一个成员函数是定义在一个类或者对象中的函数:
class Sample(){
fun foo() { prin("foo") }
}
成员函数是通过点记法进行调用的。
Sample().foo() //创建Sample实例,并且调用方法foo
重载函数
在函数参数名称钱有尖括号标明的重载符号
fun <T> singletonList(item : T): List<T> { ... }
内联函数
扩展函数
高阶函数和Lambda
尾递归函数
Kotlin支持一直成为尾递归的函数编程类型,它允许一些算法使用循环来编写,而不是用递归,不会有堆栈溢出的风险。当一个函数被关键字tailrec所修饰并且符合要求的形式,编译器会使用快速高效的循环来优化递归:
tailrec fun findFixPoint(x: Double = 1.0): Double = if(x == Math.cos(x)) x else findFixPoint(Math.cos(x))
上面代码计算数学常量cos的fixpoint,它是简单的从1.0开始重复的调用Math.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进行修饰,函数规定必须在最后执行调用函数本身的操作,如果在递归调用函数本身之后,还有其他的操作,是不允许使用tailrec进行修饰的,并且不能在tra/catch/finally块中调用进行递归操作,当前的尾递归只支持JVM。