Kotlin系列四:標準函數、擴展函數、高階函數、內聯函數

目錄

一 標準函數

1.1 let

1.2  with

1.3 run

1.4 apply

 二 擴展函數

2.1 擴展函數基本使用

2.2 運算符重載

2.3 最佳實踐:擴展函數和運算符重載的合體

三  Kotlin高階函數

3.1 基本定義

3.2 三種用法

3.2.1 雙冒號 ::method

3.2.2 匿名函數

3.2.3 Lambda 表達式(常用)

四 內聯函數inline

4.1 noinline

4.2 crossinline


一 標準函數

Kotlin 的標準函數指的是 Standara.kt 文件中定義的函數,任何Kotlin 代碼都可以自由調用所有的標準函數。

1.1 let

函數式 { 參數 : 參數類型 -> 函數體 } ,它的參數就是傳入本體,我們可以在函數體內對本體做任何事情。

fun doStudy(study: Study?){
    study?.let {
        it.doHomework()
        it.readBooks()
    }
}

1.2  with

with 函數接收兩個參數,第一個參數可以是一個任意類型的對象,第二個參數是Lambda表達式。with 函數會在Lambda 表達式中提供第一個參數對象的上下文,並使用Lambda 表達式的最後一行代碼作爲返回值返回。

語法:

with(obj){
            // 這裏是 ojb 的上下文
            "value" // with 函數的返回值
        }

例子,例如創建一個水果列表,將水果列表全部打印出來:

fun printFruits(){
        val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
        val buffer = StringBuilder()
        buffer.append("Start eating fruits. \n")
        for (fruit in list) {
            buffer.append(fruit).append("\n")
        }
        buffer.append("Ate all fruits.")
        val result = buffer.toString()
        println(result)
    }

觀察上面的代碼我們可以看到多次調用了builder 對象的方法,其實這個時候就可以使用with 函數來讓代碼變得更加簡單:

fun printFruits(){
    val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
    val result = with(StringBuilder()) {
        append("Start eating fruits. \n")
        for (fruit in list) {
            append(fruit).append("\n")
        }
        append("Ate all fruits.")
        toString()
    }
    println(result)
}

在第一個參數傳入了什麼對象,那麼Lambda 表達式內就會擁有這個對象的所有變量和函數,就相當於在對象內部調用函數,所以我們直接調用了StringBuilder 對象的append 函數。

1.3 run

run函數和with函數幾乎一模一樣,with 函數是內置函數形式調用with(obj){} ,run 函數是 obj.run{} ,一個是通過傳入對象,一個是通過對象調用,作用相同,也是Lambda表達式內包含上下文環境,最後一句代碼爲返回值,run 函數只有一個參數就是Lambda 表達式。

fun printFruits(){
    val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
    val result = StringBuilder().run {
        append("Start eating fruits. \n")
        for (fruit in list) {
            append(fruit).append("\n")
        }
        append("Ate all fruits.")
        toString()
    }
    println(result)
}

1.4 apply

apply 函數和 run 函數基本相同,不同的地方在於,apply 會返回對象本身,Lambda 表達式內不存在返回值,也是在Lambda 表達式中提供對象的上下文,結構爲 obj.apply{}。

fun printFruits(){
    val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
    val result = StringBuilder().apply {
        append("Start eating fruits. \n")
        for (fruit in list) {
            append(fruit).append("\n")
        }
        append("Ate all fruits.")
    }
    println(result.toString())
}

 二 擴展函數

2.1 擴展函數基本使用

Kotlin 能夠擴展一個類的新功能而無需繼承該類或者使用像裝飾者這樣的設計模式。 這通過叫做 擴展 的特殊聲明完成。 例如,你可以爲一個你不能修改的、來自第三方庫中的類編寫一個新的函數。 這個新增的函數就像那個原始類本來就有的函數一樣,可以用普通的方法調用。 這種機制稱爲擴展函數 。此外,也有擴展屬性 , 允許你爲一個已經存在的類添加新的屬性。

建議向哪個類中添加擴展函數,就定義一個同名的Kotlin文件,便於以後查找。當然擴展函數可以定義在任何一個現有類中,並不一定非要創建新文件。不過通常而言,最好定義成頂層方法,這樣可以讓擴展函數擁有全局的訪問域。

語法結構

fun ClassName.methodName(param1: Int, param2: Int): Int {
    //相關邏輯
    return 0
}

聲明一個擴展函數,我們需要用一個 接收者類型 也就是被擴展的類型來作爲他的前綴。 下面代碼爲 MutableList<Int> 添加一個swap 函數:

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // “this”對應該列表
    this[index1] = this[index2]
    this[index2] = tmp
}

現在,我們可以對任意 MutableList<Int> 調用該函數了:

val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // “swap()”內部的“this”會保存“list”的值

當然,這個函數對任何 MutableList<T> 起作用,我們可以泛化它:

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // “this”對應該列表
    this[index1] = this[index2]
    this[index2] = tmp
}

注意:

  • 擴展函數在不修改某個類源碼的情況下,動態地添加新的函數.className.
  • 擴展函數不能訪問原有類的私有屬性
  • 底層實際上是用寫了個靜態函數來實現的,將類的實例傳入這個靜態函數,然後搞一些操作

2.2 運算符重載

概念:同一運算符在不同的環境所表現的效果不同,如”+“在兩個Int值之間表示兩者的數值相加,在兩個字符串之間表示,將字符串拼接,同時kotlin允許我們將任意兩個類型的對象進行”+“運算,或者其他運算符操作。


語法結構:如下,其中operator 爲運算符重載的關鍵字

class A {
    operator fun plus(a: A): A {
        //相關邏輯
    }
}

常見的語法糖表達式和實際調用函數對照表:

表達式 函數名
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b)
a + b a.plus(b)
a - b a.minus(b)
a++ a.inc()
a-- a.dec()
!a a.not()
a == b a.equals(b)
”a > b“、”a < b“、”a >= b“、”a >= b“ a.compareTo(b)
a..b a.rangeTo(b)
a[b] a.get(b)
a[b] = c a.set(b, c)
a in b b.contains(a)

2.3 最佳實踐:擴展函數和運算符重載的合體

operator fun ClassName.plus(param1: ClassName): ClassName {
    //相關邏輯
    return result
}

舉例:

fun main() {
     "hello " * 6
}
operator fun String.times(int:Int){
    val builder = StringBuilder()
    repeat(int){
        builder.append(this)
    }

    println(builder.toString())
}

三  Kotlin高階函數

3.1 基本定義

函數類型定義:

(String, Int) -> Unit

高階函數定義:參數有函數類型或者返回值是函數類型的函數。

fun a(funParam: (Int) -> String): String {
  return funParam(1)
}

要傳一個函數類型的參數,或者把一個函數類型的對象賦值給變量有三種方法

fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
    val result = operation(num1, num2)
    return result
}

3.2 三種用法

3.2.1 雙冒號 ::method

在 Kotlin 裏,一個函數名的左邊加上雙冒號,它就不表示這個函數本身了,而表示一個對象,或者說一個指向對象的引用,但,這個對象可不是函數本身,而是一個和這個函數具有相同功能的對象。下面的例子表示將plus()函數作爲參數傳遞給num1AndNum2()函數。

fun plus(num1: Int, num2: Int) : Int {
    return num1 + num2
}
val a = num1AndNum2(10, 8, ::plus)

3.2.2 匿名函數

val a = num1AndNum2(10, 8, fun(num1: Int, num2: Int) = num1 + num2)

3.2.3 Lambda 表達式(常用)

val a = num1AndNum2(10, 8) {
    n1, n2 -> n1 + n2
} 

Kotlin高階函數的實現原理是將Lambda表達式在底層轉爲如上第二種匿名類的實現方式。每調用一次Lambda都會創建一個新的匿名類實例,會造成額外的內存和性能開銷。由此Kotlin提供了內聯函數功能。


四 內聯函數inline

Kotlin編譯器會將內聯函數中的代碼在編譯時自動替換到調用它的地方,這樣就不存在運行時的開銷了。一般會把高階函數聲明爲內聯函數,即在定義高階函數時加上inline關鍵字聲明,這是一種良好的編程習慣,絕大多數高階函數是可以直接聲明成內聯函數的。

inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
    val result = operation(num1, num2)
    return result
}

4.1 noinline

如果一個內聯高階函數中含有多個函數類型參數,其中有一個函數類型參數不想內聯,可在該參數前加上noinline關鍵字。

inline fun inlineFun(block1:() -> Unit, noinline block2: () -> Unit) {
}

爲什麼Kotlin還提供一個noline關鍵字來排除內聯功能?

因爲內聯的函數類型參數在編譯的時候會被代碼替換,因此它沒有真正的參數屬性。非內聯的函數類型參數可以自由地傳遞給其他函數,因爲它就是一個真實的參數,而內聯的參數類型只允許傳遞給另外一個內聯函數,這是它最大的侷限性。

內聯函數和非內聯函數的另一個區別是內聯函數所引用的Lambda表達式中可以使用return關鍵字進行函數返回,而非內聯函數只能局部返回。(注意,這個有點拗口,因爲在Java8語言中,lambda表達式是可以明確使用return關鍵字的,而Kotlin語言中,lambda表達式卻不能使用return關鍵字。而上面內聯函數所引用的Lambda表達式中可以使用return關鍵字進行的函數返回是表示退出該lambda函數邏輯,局部返回,不再執行Lambda表示式的剩餘內容。)

4.2 crossinline

如果在內聯高階函數中創建了另外的Lambda或者匿名類的實現,並且在這些實現中調用函數的參數,就會提示錯誤。如

inline fun runRunnable(block: () -> Unit) {
    val runnable = Runnable {
        block() // 提示錯誤
    }
}

因爲內聯函數所引用的Lambda表達式允許使用return進行函數返回,而高階函數的匿名類實現中不允許使用return造成了衝突。

爲什麼高階函數的匿名類實現中不允許使用return?匿名類中調用的函數類型參數是不可能進行外層調用函數返回的(匿名類啊,你連名字都沒有你怎麼在外部調用?),最多隻能對匿名類中的函數調用進行返回。所以高階函數的匿名類實現中不允許使用return。

crossinline用於保證內聯函數Lambda表達式中一定不會使用return進行函數返回。

inline fun runRunnable(crossinline block: () -> Unit) {
    val runnable = Runnable {
        block() 
    }
}


聲明後runRunnable函數的Lambda中無法使用return進行函數返回了,仍然可以使用return@runRunnable進行局部返回

reified 關鍵字
reified 配合 inline可以將泛型實例化

inline fun <reified T> startActivity(context: Context, block: Intent.() -> Unit) {
    val intent = Intent(context, T::class.java)
    intent.block()
    context.startActivity(intent)
}

 

注:本文主要例子和參考

郭霖《第一行代碼》 Kotlin部分

Kotlin官網 https://www.kotlincn.net/docs/reference/basic-syntax.html

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章