函數聲明(Function Declarations)
在Kotlin中,函數的聲明使用fun
關鍵字:
fun double(x: Int): Int {
}
函數用法(Function Usage)
使用傳統(tǒng)的方法調用函數:
val result = double(2)
使用.
操作符調用成員函數:
Simple().foo() //創(chuàng)建累的實例并調用foo()方法
中綴符號(Infix notation)
當滿足下列條件的時候,函數還可以使用中綴符號調用:
- 當函數是成員函數或者擴展函數的時候
- 當他們僅有一個參數的時候
- 當他們通過
infix
關鍵字標記的時候
如:
// 為Int定義擴展函數
infix fun Int.shl(x:Int):Int{
...
}
//使用中綴符號調用擴展函數
1 shl 2
//等同于
1.shl(2)
參數(Parameters)
函數參數使用Pascal表示法定義,也就是名字:類型
。參數之間使用逗號表示。每一個參數都必須顯式指明類型。
fun powerOf(number:Int,exponent:Int){
...
}
默認參數(Default Arguments)
函數的參數可以擁有默認值,當省略相應的參數時使用默認值。同其他語言相比,這將減少重載方法的數量:
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) { ... } // no default value allowed
}
參數指定(Named Arguments)
當調用函數的時候,函數參數可以被命名。當一個函數有非常多的參數且一部分具有默認值的時候,這非常方便。
給定如下函數:
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 = '_')
注意:指定參數語法不能在調用Java函數的時候使用,因為Java字節(jié)碼不總是保留函數參數的名稱。
返回Unit類型的函數(Unit-returning functions)
如果一個函數不需要返回任何有用的類型的值,則其可以返回Unit類型。Unit
是只有一個Unit
值的類型。該值不需要顯式返回任何值:
fun printHello(name: String?): Unit {
if (name != null)
println("Hello ${name}")
else
println("Hi there!")
// `return Unit` or `return` is optional
}
Unit類型的返回聲明是可選的。上述代碼等同于:
fun printHello(name: String?) {
...
}
單表達式函數(Single-Expression functions)
當一個函數僅返回一個表達式,則可以省略花括號,函數體可以直接跟在=號后面:
fun double(x: Int): Int = x * 2
若編譯器可以推斷出返回值類型,則返回值類型是可以省略的:
fun double(x: Int) = x * 2
顯式返回值類型(Explicit return types)
函數體是代碼塊的函數必須顯式指定返回值類型,除非返回值類型是Unit,則是可以省略的。Kotlin不能推斷函數體是代碼塊的函數的返回值類型,因為這樣的函數在代碼塊中可能有復雜的流程控制,返回值類型對于讀者而言可能不是明確的(有時甚至于對編譯器而言)
可變數量的參數(Variable number of arguments 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參數不是參數列表中的最后一個,則可以使用指定參數名語法來傳入vararg
參數之后的參數,如果參數具有函數類型,則可以在括號外邊傳入λ表達式。
當我們調用一個vararg函數的時候,我們可以一個接一個的傳參,如asList(1,2,3)
,如果我們已經有一個數組,并想將其作為參數傳入函數,我們可以使用*操作符將數組傳入其中:
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
函數作用域(Function Scope)
在Kotlin中,函數可以在文件的頂層級聲明,意味著如果你不必創(chuàng)建一個類只為持有一個函數,像Java,C#或Scala。除了頂級函數之外,Kotlin函數也可以被聲明為局部函數,成員函數或擴展函數。
局部函數(Local Functions)
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())
}
局部函數可以訪問外部函數(閉包1和閉包2)的局部變量,因此在上例中,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])
}
成員函數(Member Fuctions)
一個成員函數是定義在類或對象內部的函數:
class Sample() {
fun foo() { print("Foo") }
}
成員函數可以通過.符號調用:
Sample().foo() // creates instance of class Sample and calls foo
更多關于類和成員重寫的知識參見類和繼承。
泛型函數(Generic Functions)
函數可以使用泛型參數,泛型參數在函數名之前使用尖括號指定:
fun <T> singletonList(item: T): List<T> {
// ...
}
內聯函數(Inline Functions)
內聯函數參見這里
擴展函數
擴展函數參見這里
高階函數和λ表達式(Higher-Order Functions and Lambads)
高階函數和λ表達式參見他們的模塊。
尾遞歸函數(Tail recursive functions)
Kotlin 支持一種稱為尾遞歸的函數式編程風格。 這允許一些通常用循環(huán)寫的算法改用遞歸函數來寫,而無堆棧溢出的風險。 當一個函數用 tailrec 修飾符標記并滿足所需的形式時,編譯器會優(yōu)化該遞歸,留下一個快速而高效的基于循環(huán)的版本。
tailrec fun findFixPoint(x: Double = 1.0): Double
= if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
這段代碼計算余弦的不動點(fixpoint of cosine),這是一個數學常數。 它只是重復地從 1.0 開始調用 Math.cos,直到結果不再改變,產生0.7390851332151607的結果。最終代碼相當于這種更傳統(tǒng)風格的代碼:
private fun findFixPoint(): Double {
var x = 1.0
while (true) {
val y = Math.cos(x)
if (x == y) return y
x = y
}
}
要符合 tailrec
修飾符的條件的話,函數必須將其自身調用作為它執(zhí)行的最后一個操作。在遞歸調用后有更多代碼時,不能使用尾遞歸,并且不能用在 try/catch/finally
塊中。目前尾部遞歸只在 JVM 后端中支持。