函數和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安卓開發