kotlin函數和Lambda表達式——>函數
函數:
1.函數聲明
kotlin中的函數使用fun關鍵字聲明:
fun double(x: Int): Int { return 2 * x }
2.函數用法
調用函數使用傳統的方法:
val result = double(2)
調用成員函數使用點表示法:
Stream().read() // 創建類 Stream 實例并調用 read()
3. 參數
函數參數使用 Pascal 表示法定義,即 name: type。參數用逗號隔開。每個參數必須有顯式類型:
fun powerOf(number: Int, exponent: Int) { /*......*/ }
4.默認參數
函數參數可以有默認值,當省略相應的參數時使用默認值。與其他語言相比,這可以減少重載數量
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 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(qux = { println("hello") }) // 使用兩個默認值 bar = 0 與 baz = 1 foo { println("hello") } // 使用兩個默認值 bar = 0 與 baz = 1
5.具名參數
可以在調用函數時使用具名的函數參數。當一個函數有大量的參數或默認參數時這會非常方便。
給定以下函數:
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"))
//對于 JVM 平臺:在調用 Java 函數時不能使用具名參數語法,因為 Java 字節碼并不總是保留函數 參數的名稱。
6.返回Unit的函數
如果一個函數不返回任何有用的值,它的返回類型是 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?) { ...... }
7.單表達式函數
當函數返回單個表達式時,可以省略花括號并且在 = 符號之后指定代碼體即可:
fun double(x: Int): Int = x * 2
當返回值類型可由編譯器推斷時,顯式聲明返回類型是可選的:
fun double(x: Int) = x * 2
8.顯示返回類型
具有塊代碼體的函數必須始終顯式指定返回類型,除非他們旨在返回 Unit,在這種情況下它是可選 的。Kotlin 不推斷具有塊代碼體的函數的返回類型,因為這樣的函數在代碼體中可能有復雜的控制流,
并且返回類型對于讀者(有時甚至對于編譯器)是不明顯的。
9.可變數量的參數(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)
在函數內部,類型 T 的 vararg 參數的可?方式是作為 T 數組,即上例中的 ts 變量具有類型Array <out T>。
只有一個參數可以標注為 vararg 。如果 vararg 參數不是列表中的最后一個參數,可以使用具名參數語法傳遞其后的參數的值,或者,如果參數具有函數類型,則通過在括號外部傳一個 lambda。
當我們調用 vararg -函數時,我們可以一個接一個地傳參,例如 asList(1, 2, 3) ,或者,如果我們已經有一個數組并希望將其內容傳給該函數,我們使用伸展(spread)操作符(在數組前面加 * ):
val a = arrayOf(1, 2, 3) val list = asList(-1, 0, *a, 4)
10.中綴表示法
//標有 infix 關鍵字的函數也可以使用中綴表示法(忽略該調用的點與圓括號)調用。中綴函數必須滿足 以下要求: //— 它們必須是成員函數或擴展函數; //— 它們必須只有一個參數; //— 其參數不得接受可變數量的參數且不能有默認值。 infix fun Int.shl(x: Int): Int { ...... } // 用中綴表示法調用該函數 1 shl 2 // 等同于這樣 1.shl(2) //中綴函數調用的優先級低于算術操作符、類型轉換以及 rangeTo 操作符。以下表達式是等價 的: //— 1 shl 2 + 3等價于1 shl (2 + 3) //— 0 until n * 2等價于0 until (n * 2) //— xs union ys as Set<*>等價于xs union (ys as Set<*>) //另一方面,中綴函數調用的優先級高于布爾操作符 && 與 ||、is- 與 in- 檢測以及其他一些操 作符。這些表達式也是等價的: //— a && b xor c等價于a && (b xor c) //— a xor b in c等價于(a xor b) in c
請注意,中綴函數總是要求指定接收者與參數。當使用中綴表示法在當前接收者上調用方法時,需要顯 式使用 this ;不能像常規方法調用那樣省略。這是確保非模糊解析所必需的
class MyStringCollection { infix fun add(s: String) { /*......*/ } fun build() { this add "abc"// 正確 add("abc")// 正確 add "abc" // 錯誤:必須指定接收者 } }
11.函數作用域
在 Kotlin 中函數可以在文件頂層聲明,這意味著你不需要像一些語言如 Java、C# 或 Scala 那樣需要創 建一個類來保存一個函數。此外除了頂層函數,Kotlin 中函數也可以聲明在局部作用域、作為成員函數 以及擴展函數。
12.局部函數
Kotlin 支持局部函數,即一個函數在另一個函數內部:
fun dfs(graph: Graph) { fun dfs(current: Vertex, visited: MutableSet<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]) }
13.成員函數
成員函數是在類或對象內部定義的函數:
class Sample { fun foo() { print("Foo") } }
成員函數以點表示法調用:
Sample().foo() // 創建類 Sample 實例并調用 foo
14.泛型函數
函數可以有泛型參數,通過在函數名前使用尖括號指定:
fun <T> singletonList(item: T): List<T> { /*......*/ }
15.尾遞歸函數
Kotlin支持一種稱為尾遞歸的函數式編程風格。這允許一些通常用循環寫的算法改用遞歸函數來寫,而無堆棧溢出的風險。當一個函數用tailrec修飾符標記并滿足所需的形式時,編譯器會優化該遞歸,留下一個快速而高效的基于循環的版本:
val eps = 1E-10 // "good enough", could be 10^-15 tailrec fun findFixPoint(x: Double = 1.0): Double = if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))
這段代碼計算余弦的不動點(fixpoint of cosine),這是一個數學常數。它只是重復地從 1.0 開始調用 Math.cos,直到結果不再改變,對于這里指定的 eps 精度會產生 0.7390851332151611 的結果。最終代碼相當于這種更傳統?格的代碼:
val eps = 1E-10 // "good enough", could be 10^-15 private fun findFixPoint(): Double { var x = 1.0 while (true) { val y = Math.cos(x) if (Math.abs(x - y) < eps) return x x = Math . cos (x) } } //要符合 tailrec 修飾符的條件的話,函數必須將其自身調用作為它執行的最后一個操作。 //在遞歸調用后有更多代碼時,不能使用尾遞歸,并且不能用在try/catch/finally 塊中。 //目前在 Kotlin for JVM 與 Kotlin/Native 中支持尾遞歸。

浙公網安備 33010602011771號