函數和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)
作為一個函數來調用,就是函數類型?;蛘呖梢杂懈Z義化的參數名稱:
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高級篇