Kotlin讀書筆記之內聯函數、擴展函數、匿名函數、lambda

本文主要涉及內聯函數、擴展函數、lambada以及匿名函數等。作爲讀書筆記對於細節深入沒有過多的擴展,後續將對於各個知識點作進一步的研度。本文的內容主要是參考官方教程以及博客內容,作爲讀書筆記以及後續知識點擴展的一個大綱。學海無涯,希望能在Android這條路上越走越遠!

1.內聯函數

1.1 內聯函數的使用

首先看個例子,一個是用了inline修飾,另一個沒有:

    init{
        functionWithInline()
        functionWithoutInline()
    }

    inline fun functionWithInline(){
        println("functionWithInline:執行具體代碼")
    }

    fun functionWithoutInline(){
        println("functionWithoutInline:執行具體代碼")
    }

接下來看下反編譯出來的java代碼

public final void functionWithInline() {
      int $i$f$functionWithInline = 0;
      String var2 = "functionWithInline:執行具體代碼";
      boolean var3 = false;
      System.out.println(var2);
   }

   public final void functionWithoutInline() {
      String var1 = "functionWithoutInline:執行具體代碼";
      boolean var2 = false;
      System.out.println(var1);
   }

   public test() {
      int $i$f$functionWithInline = false;
      String var3 = "functionWithInline:執行具體代碼";
      boolean var4 = false;
      System.out.println(var3);
      this.functionWithoutInline();
   }

可以看到functionWithInline處的執行代碼直接複製到了test()調用處,也就是說inline修飾的話直接是把嵌入對應函數的代碼,減少了普通函數調用過程中壓彈棧過程,提高了效率。

1.2 禁用內聯

如果內聯函數中的某個參數不想內聯,那麼可以用noinline修飾。

inline fun sum(a: Int, b: Int, lambda: (result: Int) -> Unit, noinline lambda2: (result: Int) -> Unit): Int {
    val r = a + b
    lambda.invoke(r)
    lambda2.invoke(r)
    return r
}

fun main(args: Array<String>) {
    sum(1, 2,
            { println("Result is: $it") },
            { println("Invoke lambda2: $it") }
    )
}

反編譯之後的代碼爲:

public static final int sum(int a, int b, @NotNull Function1 lambda, @NotNull Function1 lambda2) {
   int r = a + b;
   lambda.invoke(r);
   lambda2.invoke(r);
   return r;
}

public static final void main(@NotNull String[] args) {
   byte a$iv = 1;
   byte b$iv = 2;
   Function1 lambda2$iv = (Function1)null.INSTANCE;
   int r$iv = a$iv + b$iv;
   String var8 = "Result is: " + r$iv;
   System.out.println(var8);
   lambda2$iv.invoke(r$iv);

可以看到只有lambda的代碼拷貝過來了,而noinline修飾的lambda2並沒有嵌入到調用處。

1.3 非局部返回

首先,在 Kotlin 中,我們只能對具名或匿名函數使用正常的、非限定的 return 來退出。下面以一個例子來說明:

fun main() {
    test {
        return //error: 'return' is not allowed here
        println("test")
    }
}

fun test(body: () -> Unit) {
    body()
}

lambda不能直接使用return,那麼有什麼方法呢?內聯函數是可以用retrun返回:

fun main() {
    test {
        println("test")
        return
    }
}

inline fun test(body: () -> Unit) {
    body()
}

有一種情況,如果函數作爲內聯函數的參數,那麼我們可以禁止其使用return。如下例子所示:

fun main() {
    var result = sum(1, 2){
         value ->
         	println("First result is: $value")
         	return 
        }
    result *= 10
    println("Second result is: $result")
}

inline fun sum(a: Int, b: Int, lambda: (result: Int) -> Unit): Int {
    val r = a + b
    lambda.invoke(r)
    return r
}

結果:First result is: 3

可以看到結果只是First result is: 3,也就是說下面的第二個打印被屏蔽掉了。然後有時候並不想因爲外部的lambda影響到調用處。那如果想要屏蔽這個返回要怎麼做呢?

fun main() {
    var result = sum(1, 2){
         value ->
         println("First result is: $value")
         return //error: 'return' is not allowed here
        }
    result *= 10
    println("Second result is: $result")
}

inline fun sum(a: Int, b: Int, crossinline lambda: (result: Int) -> Unit): Int {
    val r = a + b
    lambda.invoke(r)
    return r
}

加了crossinline之後在return那一行會報錯error: ‘return’ is not allowed here。也就是說crossinline限制了return的返回。

1.4 具體化的類型參數

衆所周知,泛型在運行時會有個擦除的動作。也就是說像is這樣子的判斷是不可以用的。那麼就有個問題,如果要判斷某個變量是否數據某個類型就很麻煩。kotlin提供了具體化的類型參數。首先該函數必須是內聯的,如下所示:

fun main() {

    jisuan<Int>()
    jisuan<String>()
}

inline fun <reified T> jisuan(){
    var p = 2
    if(p !is T){
        println("p is not the type = "+p)
    }else{
        println("p is the type = "+p)
    }
}

例子中泛型前面加了reified修飾符,那麼該函數的正常的操作符如 !isas 現在都能用了。

1.5 內聯屬性

內聯屬性就是 修飾符可用於沒有幕後字段的屬性的訪問器,也可以標註整個屬性。

val foo: Foo
    inline get() = Foo()

var bar: Bar
    get() = ……
    inline set(v) { …… }
    
    inline var bar: Bar
    get() = ……
    set(v) { …… }

2.擴展函數

擴展函數,第一個想到的是裝飾器模式。在不破壞原有代碼封裝性的前提下對功能做了增強。如下所示:

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

MutableList做了數據交換的功能增強。那麼如果要交換數據可以如下操作:

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

2.1 擴展是靜態解析

擴展是靜態分發的。比如說父類定義了擴展函數,子類也定義了擴展函數。而父類的擴展函數並不能被子類的擴展函數所覆蓋:

open class Shape

class Rectangle: Shape()

fun Shape.getName() = "Shape"

fun Rectangle.getName() = "Rectangle"

fun printClassName(s: Shape) {
    println(s.getName())
}    

printClassName(Rectangle())

如果被擴展的函數本身以及擴展函數擁有着相同的簽名,那麼優先取成員函數

class Example {
    fun printFunctionType() { println("Class method") }
}

fun Example.printFunctionType() { println("Extension function") }

Example().printFunctionType()

2.2 可以爲可空的接收者類型定義擴展

這個從理解成裝飾器模式。被裝飾的對象爲空也理應存在的:

fun Any?.toString(): String {
    if (this == null) return "null"
    // 空檢測之後,“this”會自動轉換爲非空類型,所以下面的 toString()
    // 解析爲 Any 類的成員函數
    return toString()
}

2.3 擴展屬性

kotlin支持屬性的擴展:

val <T> List<T>.lastIndex: Int
    get() = size - 1

擴展屬性不能有初始化器,如下行爲則會報錯:

val House.number = 1 // 錯誤:擴展屬性不能有初始化器

2.4 伴生對象的擴展

class MyClass {
    companion object { }  // 將被稱爲 "Companion"
}

fun MyClass.Companion.printCompanion() { println("companion") }

fun main() {
    MyClass.printCompanion()
}

2.5 擴展的作用域以及擴展聲明爲成員

作用域:如果是同一個包裏面直接使用即可。而如果在別的包裏面,那麼得用import導入。
擴展聲明爲成員:在官方的文檔中描述爲分發接收者以及擴展接收者 。所謂分發接收者,就是被擴展的對象。而分發接收者表示擴展定義所在函數。
擴展接收者與擴展分發者如果有相同的函數名衝突,那麼優先考慮擴展接收者。
擴展接收者是靜態的,而擴展分發者是虛擬的。也就是說擴展接收者在函數定義的時候就已經確定了,而擴展分發者是在調用的時候才確定的。

open class Base { }

class Derived : Base() { }

open class BaseCaller {
    open fun Base.printFunctionInfo() {
        println("Base extension function in BaseCaller")
    }

    open fun Derived.printFunctionInfo() {
        println("Derived extension function in BaseCaller")
    }

    fun call(b: Base) {
        b.printFunctionInfo()   // 調用擴展函數
    }
}

class DerivedCaller: BaseCaller() {
    override fun Base.printFunctionInfo() {
        println("Base extension function in DerivedCaller")
    }

    override fun Derived.printFunctionInfo() {
        println("Derived extension function in DerivedCaller")
    }
}

fun main() {
    BaseCaller().call(Base())   // “Base extension function in BaseCaller”
    DerivedCaller().call(Base())  // “Base extension function in DerivedCaller”——分發接收者虛擬解析
    DerivedCaller().call(Derived())  // “Base extension function in DerivedCaller”——擴展接收者靜態解析
}

3.高階函數和 Lambda 表達式

所謂高階函數,就是參數或者返回值是一個函數。

fun <T, R> Collection<T>.fold(
    initial: R, 
    combine: (acc: R, nextElement: T) -> R
): R {
    var accumulator: R = initial
    for (element: T in this) {
        accumulator = combine(accumulator, element)
    }
    return accumulator
}

// lambda 表達式的參數類型是可選的,如果能夠推斷出來的話:
val joinedToString = items.fold("Elements:", { acc, i -> acc + " " + i })

函數類型:

  • 一般的寫法就是參數寫在括號內:(A,B)->C
  • 有額外接收者的函數:A.(B)->C
  • suspend()協程中涉及到再介紹

如需將函數類型指定爲可空,請使用圓括號:((Int, Int) -> Int)?

函數類型可以使用圓括號進行接合:(Int) -> ((Int) -> Unit)

箭頭表示法是右結合的,(Int) -> (Int) -> Unit 與前述示例等價,但不等於 ((Int) -> (Int)) -> Unit

函數類型實例化

  • 使用函數字面值的代碼塊,採用以下形式之一:

    • lambda 表達式: { a, b -> a + b },
    • 匿名函數: fun(s: String): Int { return s.toIntOrNull() ?: 0 }

    帶有接收者的函數字面值可用作帶有接收者的函數類型的值。

  • 使用已有聲明的可調用引用:

    • 頂層、局部、成員、擴展函數:::isOddString::toInt
    • 頂層、成員、擴展屬性:List<Int>::size
    • 構造函數:::Regex

    這包括指向特定實例成員的綁定的可調用引用:foo::toString

  • 使用實現函數類型接口的自定義類的實例:

class IntTransformer: (Int) -> Int {
    override operator fun invoke(x: Int): Int = TODO()
}
val intFunction: (Int) -> Int = IntTransformer()

函數類型實例調用

val stringPlus: (String, String) -> String = String::plus
val intPlus: Int.(Int) -> Int = Int::plus

println(stringPlus.invoke("<-", "->"))
println(stringPlus("Hello, ", "world!")) 

println(intPlus.invoke(1, 1))
println(intPlus(1, 2))
println(2.intPlus(3)) // 類擴展調用

3.1 Lambda 表達式語法

Lambda的完整表達式如下所示:

val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }

等號左邊是簽名,等號右邊是具體的代碼實現。如果推斷出的該 lambda 的返回類型不是 Unit,那麼該 lambda 主體中的最後一個(或可能是單個) 表達式會視爲返回值。

3.1.1 傳遞末尾的 lambda 表達式

如果函數的最後一個參數是函數的話,則可以放在外面:

val product = items.fold(1) { acc, e -> acc * e }

3.1.2 it:單個參數的隱式名稱

如果參數只有一個話,那麼可以用it來代表這個參數的對象

ints.filter { it > 0 } // 這個字面值是“(it: Int) -> Boolean”類型的

3.1.3 從 lambda 表達式中返回一個值

可以使用限定的返回語法從 lambda 顯式返回一個值。 否則,將隱式返回最後一個表達式的值。

ints.filter {
    val shouldFilter = it > 0 
    shouldFilter
}

ints.filter {
    val shouldFilter = it > 0 
    return@filter shouldFilter
}

3.2 匿名函數

匿名函數和正常的函數類似,就是把函數名給去掉了。

fun(x: Int, y: Int): Int = x + y//等同於下面的寫法

fun(x: Int, y: Int): Int {
    return x + y
}

3.3 閉包

閉包的概念:函數裏面聲明函數,函數裏面返回函數,就是閉包

fun test():()->Unit{
    var a=3 //狀態
    return  fun(){
        a++;
        println(a);
    }
}

fun main(args: Array<String>) {
    var t=test()
    t();
    t();
    t();
}

神奇之處就是函數中的變量a的值會保存,打印出4,5,6

3.4 帶有接收者的函數字面值

Lambda寫法:

val sum: Int.(Int) -> Int = { other -> plus(other) }

匿名函數寫法:

val sum = fun Int.(other: Int): Int = this + other

當接收者類型可以從上下文推斷時,lambda 表達式可以用作帶接收者的函數字面值

class HTML {
    fun body() { …… }
}

fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()  // 創建接收者對象
    html.init()        // 將該接收者對象傳給該 lambda
    return html
}

html { 
    body()   // body()可以從上下文推出
}

//以上的函數換個寫法會比較容易理解
html (HTML::body)

4.尾遞歸函數

在瞭解尾遞歸函數之前,先用java寫一個遞歸函數,循環10000次:

fun main() {
    recycle(10000)
}

fun recycle(count:Int){
    println("count = "+count)
    if(count<=0){
        return
    }else{
        recycle(count - 1)
    }
}

運行到9000多次的時堆棧時會出現堆棧溢出的問題:Exception in thread "main" java.lang.StackOverflowError。而kotlin的尾遞歸函數無堆棧溢出的風險,但是有個要求就是遞歸函數必須是最後調用的。如下所示:

fun main() {
    recycle(10000)
}

tailrec fun recycle(count: Int):Unit = if (count <= 0) println("遞歸完成") else {
    println("count = "+count)
    recycle(count - 1)
    
    println("count = "+count)//如果遞歸的函數後還有代碼出現,會出現警告
}

運行的結果是,用kotlin尾遞歸函數的話,是不會出現堆棧溢出的。

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