函数和lambda表达式
- 函数声明
fun double(x: Int): Int {
return 2*x
}
- 函数调用
val result = double(2)
Sample().foo() // 创建类 Sample的对象,并调用foo()函数
- 默认参数
fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) {
......
}
read("ab".toByteArray().toTypedArray())
- 命名参数
fun reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
println("str:$str, " +
"normalizeCase:$normalizeCase, " +
"upperCaseFirstLetter:$upperCaseFirstLetter, " +
"divideByCamelHumps:$divideByCamelHumps, " +
"wordSeparator:$wordSeparator")
}
reformat("hello",
normalizeCase = true,
divideByCamelHumps = false,
wordSeparator = '_')
reformat("hello", wordSeparator = '-')
- 可变数量的参数(Varargs)
函数的参数(通常是最后一个)可以用 vararg 修饰符标记:
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)
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
*
可以将数字或集合展开
- 局部函数
局部函数可以访问外部函数(即闭包)的局部变量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])
}
lambda表达式和匿名函数
一个 lambda 表达式或匿名函数是一个函数类型的字面值,即一个未声明的函数, 但立即做为表达式传递。考虑下面的例子:
max(strings, { a, b -> a.length < b.length })
函数 max
是一个高阶函数,换句话说它接受一个函数作为第二个参数。 其第二个参数是一 个表达式,它本身是一个函数,即函数字面值。写成函数的话,它相当于
fun compare(a: String, b: String): Boolean = a.length < b.length
函数类型
fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
var max: T? = null
for (it in collection)
if (max == null || less(max, it))
max = it
return max
}
在上面的 max
函数中参数less
的类型是 (T, T) -> Boolean
,即一个接受两个类型 T
的参数并返回一个布尔值的函数,参数less(max, it)
作为一个函数来调用,就是函数类型。或者可以有更语义化的参数名称:
less: (left: T, right: T) -> Boolean
lamda表达式
- 函数类型的字面值
- lambda 表达式总是被大括号括着
- 其参数(如果有的话)在
->
之前声明(参数类型可以省略) - 函数体(如果存在的话)在
->
后面 - 返回值,如果推断出的该lambda的返回类型不是
Unit
,那么该lambda主体中的最后一个表达式会视为返回值
val compare: (T, T) -> Boolean = { a, b -> a.length < b.length }
在 Kotlin 中有一个约定,如果函数的最后一个参数是一个函数,并且你传递一个 lambda 表达 式作为相应的参数,你可以在圆括号之外指定它:
max(strings) { a, b -> a.length < b.length }
请注意,如果 lambda 是该调用的唯一参数,则调用中的圆括号可以完全省略。
val doubled = ints.map { value -> value * 2 }
it :单个参数的隐式名称
ints.map { it * 2 }
下划线用于未使用的变量
map.forEach { _, value -> println("$value!") }
限定的返回语法从lambada显示返回一个值,否则将隐式的返回最后一个表达式的值,因此一下片段是等价的:
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
匿名函数
上面提供的 lambda 表达式语法缺少的一个东西是指定函数的返回类型的能力。在大多数情况 下,这是不必要的。因为返回类型可以自动推断出来。然而,如果确实需要显式指定,可以 使用另一种语法: 匿名函数 。
fun(x: Int, y: Int): Int = x + y
//或者下面的形式
fun(x: Int, y: Int): Int {
return x + y
}
匿名函数看起来非常像一个常规函数声明,唯一的区别是省略了名称。
请注意,匿名函数参数总是在括号内传递。 允许将函数留在圆括号外的简写语法仅适用于 lambda 表达式。
Lambda表达式和匿名函数之间的另一个区别是非局部返回
的行为。一个不带标签的return
语句总是在用fun
关键字声明的函数中返回。这意味着 lambda 表达式中的return
将从包 含它的函数返回,而匿名函数中的return
将从匿名函数自身返回。
为了方便记忆,我是这么理解函数类型的,函数类型
和 函数字面值
的关系:函数类型
就好比java的接口
,是一个函数的模板
,而函数字面值好比函数对象
。ps:完全是个人的理解,你也可以有你自己的理解。
- 高阶函数
高阶函数是将函数用作参数或返回值的函数。下面的lock()函数接受一个锁对象和一个函数,获取锁,运行函数并释放锁:
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
} finally {
lock.unlock()
}
}
在上面的函数中,body
拥有函数类型: () -> T
, 所以它应该是一个不带参数并且返回 T 类型值的函数。它在 try
-代码块内部调用、被 lock
保护,其结果由 lock() 函数返回。
如果我们想调用 lock() 函数,我们可以把另一个函数传给它作为参数:
fun toBeSynchronized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized)
通常会更方便的另一种方式是传一个 lambda 表达式:
val result = lock(lock, { sharedResource.operation() })
高阶函数的另一个例子是 map() :
fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
val result = arrayListOf<R>()
for (item in this)
result.add(transform(item))
return result
}
val doubled = ints.map { value -> value * 2 }
- 内联函数
使用高阶函数会带来一些运行时的效率损失:每一个函数都是一个对象,并且会捕获一个闭 包。 即那些在函数体内会访问到的变量。 内存分配(对于函数对象和类)和虚拟调用会引入 运行时间开销。
但是在许多情况下通过内联化 lambda 表达式可以消除这类的开销。
上面的lock()
函数可以很容易地在调用处内联。 考虑下面的情况:
lock(l) { foo() }
编译器没有为参数创建一个函数对象并生成一个调用。取而代之,编译器可以生成以下代码:
l.lock()
try {
foo()
}
finally {
l.unlock()
}
为了让编译器这么做,我们需要使用 inline
修饰符标记 lock()
函数:
inline fun lock<T>(lock: Lock, body: () -> T): T {
// ......
}
内联可能导致生成的代码增加,但是如果我们使用得当(不内联大函数),它将在性能上有所提升,尤其是在循环中的“超多态(megamorphic)”调用处。
部分内联,noinline 修饰符标记某个函数参数禁用内联:
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
// ......
}
- 扩展函数
声明一个扩展函数,我们需要用一个 接收者类型 也就是被扩展的类型来作为他的前缀。 下面
代码为 MutableList<Int> 添加一个 swap 函数:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”对应该列表
this[index1] = this[index2]
this[index2] = tmp
}
val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // “swap()”内部的“this”得到“l”的值
扩展是静态解析的
扩展不能真正的修改他们所扩展的类。通过定义一个扩展,你并没有在一个类中插入新成 员, 仅仅是可以通过该类型的变量用点表达式去调用这个新函数如下所示:
open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
printFoo(D())
上面将输出 c
,你也可以理解为扩展函数不具备多态的功能。
优先调用成员函数,如果一个类定义有一个成员函数和一个扩展函数,而这两个函数又有相同的接收者类型、相同的名字并且都适用给定的参数,这种情况总是取成员函数:
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }
C().foo() //将输出``member``
扩展的作用域
大多数时候我们在顶层定义扩展,即直接在包里:
package foo.bar
fun Baz.goo() { ...... }
要使用所定义包之外的一个扩展,我们需要在调用方导入它:
package com.example.usage
import foo.bar.goo // 导入所有名为“goo”的扩展
// 或者
import foo.bar.* // 从“foo.bar”导入一切
fun usage(baz: Baz) {
baz.goo()
}
- 尾递归函数
Kotlin 支持一种称为尾递归的函数式编程风格。 这允许一些通常用循环写的算法改用递归函 数来写,而无堆栈溢出的风险。 当一个函数用 tailrec 修饰符标记并满足所需的形式时,编译器会优化该递归,留下一个快速而高效的基于循环的版本。
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
上面的代码等价于下面的代码:
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if (x == y)
return y
x = y
}
}
尾递归的条件:
- 调用自身的操作必须是最后一个执行。
- 不能用在
try/catch/finally
块中。
注意目前只在 JVM 后端中支持
您如需了解kotlin更高级的特性如:反射、协程、类型安全的构建器、注解,请看:kotlin高级篇