函数和lambda表达式
Kotlin学习之-6.1 函数
函数定义
Kotlin中函数使用关键字fun
定义的
fun double(x: Int) : Int {
return 2*x
}
函数的使用
调用函数使用传统的方式:
val result = double(2)
调用成员函数使用点操作符
// 创建Sample类的实例并调用foo函数
Sample().foo()
内建注解
函数调用也可以使用内建注解的方式
- 当他们是成员函数或者扩展函数时
- 当他们只有单一的参数时
- 当他们被标记为
infix
关键字时
// 定义Int的扩展函数
infix fun Int.shl(x: Int): Int {
}
// 使用内建注解的方式调用扩展函数
1 shl 2
// 这和下面的一样
1.shl(2)
参数
函数参数的定义使用Pascal注解的方式,例如name:type. 参数之间是用逗号分隔。每一个参数都必须有显式地类型
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 reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
worSeparator: Char = ' ') {
}
我们也可以使用默认参数来调用这个函数
reformat(str)
然而,当使用非默认值调用的时候,代码会是这样的:
reformat(str, true, true, false, '_')
使用命名的参数我们可以让代码可读性更好。
reformat(str,
normalizeCase = true,
upperCaseFirstLetter = true,
divideByCamelHumps = false,
wordSeparator = '_'
)
如果我们不需要多有的参数,也可以这样
reformat(str, wordSeparator = '_')
注意命名参数语法在调用Java函数的时候不能使用,因为Java字节码不总是保留了函数参数的名字。
返回Unit的函数
如果一个函数不返回任何有用的值,那么他的返回值类型是Unit
, Unit
类型仅有一个值Unit
。 这个值不用显式地从函数中返回。
printHello(name: String?): Unit {
if (name != null)
println("Hello ${name})
else
println("Hi there!")
// 可选的return
// return
// return Unit
返回Unit
类型的定义也是可选的。上述代码等同于:
fun printHello(name: String?) {
}
单一表达式函数
当一个函数返回一个单一表达式的时候,花括号可以被省略并且主体用等号来标识
fun double(x: Int): Int = x * 2
荡返回值类型可以被编译器推断出来时,定义返回值类型是可选的。
fun double(x: Int) = x * 2
显式返回值类型
有代码块主体的函数必须显式地指定返回值类型, 除非它是确定要返回Unit
类型,在这种情况是可选的。Kotlin不支持为代码块主体的函数推断返回值类型因为这样的函数在主体中可能有复杂的逻辑控制流,并且返回类型对于读者来说不够明显(有时对于编译器来说也不明显)。
可变数量的参数(Varargs)
通常函数的最后一个参数可以使用标记vararg
标识符
fun <T> asList<vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in 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)
, 或者,如果我们已经有了一个数组并且想要把数组的内容传递给函数,那么我们可以使用spread操作符(在数组前面加上*)。
val a = arrayOf(1, 2, 3)
val list = asList(-1 , 0, *a, 4)
函数范围
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)
}
dfs(graph.vertices[0])
}
成员函数
一个成员函数式定义在一个类或者对象中的函数
class Sample() {
fun foo() { print("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())
这个函数计算定点数的的余弦,是一个数学常量。 它只是简单的重复从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 y
x = y
}
}
为了让tailrec
描述符更容易理解,一个函数必须在最后一个操作中调用它自己。当在递归调用之后没有更多代码的时候,你不能使用尾递归,并且你不能再try/catch/finally 块中使用尾递归。 目前尾递归仅在JVM后端支持。
PS,我会坚持把这个系列写完,有问题可以留言交流,也关注专栏Kotlin for Android Kotlin安卓开发