Kotlin進階之函數(Functions)

函數聲明(Function Declarations)

在Kotlin中,函數的聲明使用fun關鍵字:

fun double(x: Int): Int {
}

函數用法(Function Usage)

使用傳統(tǒng)的方法調用函數:

val result = double(2)

使用.操作符調用成員函數:

Simple().foo() //創(chuàng)建累的實例并調用foo()方法

中綴符號(Infix notation)

當滿足下列條件的時候,函數還可以使用中綴符號調用:

  1. 當函數是成員函數或者擴展函數的時候
  2. 當他們僅有一個參數的時候
  3. 當他們通過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)

在函數中,類型Tvararg參數被作為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 后端中支持。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容