Kotlin的擴展函數(shù)知識點

為什么需要擴展

一個新特性的出現(xiàn)必然是為了解決之前遺留的開發(fā)問題和提升目前開發(fā)效率。擴展函數(shù)也是如此。

首先來介紹下OOP:開放封閉原則。

軟件應該是可擴展,而不可修改的。也就是對擴展開放,對修改封閉

舉個栗子: 當某個三方庫的功能無法滿足現(xiàn)有業(yè)務時需要新增功能時。最簡單的做法就是直接對庫源碼修改,但是這樣違反了開放封閉原則:對源碼修改

更合理的方案是依靠擴展。Kotlin的擴展函數(shù)很顯然能夠優(yōu)雅的解決這種問題。

擴展函數(shù)是什么

首先來看下他的使用:

fun MutableList<Int>.exchange(fromIndex:Int, toIndex:Int) { 
    val tmp = this[fromIndex]    
     this[fromIndex] = this[toIndex]     
     this[toIndex] = tmp 
 }

我們將MutableList叫做接受者(receivers),意思就是這個MutableList接受了這個函數(shù),也就是給這個類擴展了這個函數(shù)。

Java中的this叫做調用者,對于普通函數(shù)來說就是該函數(shù)所屬類的實例也就是調用者對象。由于這個函數(shù)是屬于MutableList的,所以在這個方法體中this也就是指代的MutableList。 通俗的來說擴展函數(shù)體里面的this就是receivers的類型

擴展函數(shù)怎么用

根據(jù)上面定義的擴展函數(shù)栗子,來看下這個擴展函數(shù)的用法:

val list = mutableListOf(1,3,5) 
list.exchange(1,5)

這里看到擴展函數(shù)是基于對象實例來調用的,如果希望使用靜態(tài)的方式調用又該如何寫呢?稍后講解

再談擴展函數(shù)是什么

還是回到剛剛第二個話題,這次的是什么就不是簡單的介紹了。之前有篇文章講解過新技術必然離不開性能方面的考慮。因此再來講解下他是如何實現(xiàn)擴展函數(shù)的,我們通過解析他的反編譯字節(jié)碼~~

public static final void exchange(@NotNull List $receiver, int fromIndex, int toIndex) {
        //檢查$receiver參數(shù)是否為空。receiver就是調用者
         Intrinsics.checkParameterIsNotNull($receiver, "$receiver"); 
         int tmp = ((Number)$receiver.get(fromIndex)).intValue();     
         $receiver.set(fromIndex, $receiver.get(toIndex));  
          $receiver.set(toIndex, Integer.valueOf(tmp));    
  }

可以看到該函數(shù)會變成一個靜態(tài)不可重寫的方法,并且receiver變成了第一個參數(shù)。擴展函數(shù)里的的this就是receiver參數(shù)。

public 修飾的靜態(tài)方法也就是全局方法,任何地方都可以調用到(之后詳細說)。

看來并沒有什么神奇的地方只是將擴展函數(shù)變成了一個靜態(tài)方法而已。所以性能方面是沒有影響的

擴展函數(shù)在哪里可以被使用

這里首先說明下,擴展函數(shù)定義在不同的地方效果也是不一樣的。

  • 不定義在類中,也就是類外部

可以看到上面反編譯后的擴展函數(shù)就是這種類型,被static,public,final修飾的方法會有這個特征:在同一個包中是可以共享這個擴展函數(shù)的也就是可以調用到這個擴展函數(shù)。其他包里面如果也想使用這個函數(shù)就可以import這個包中的這個函數(shù)即可。

  • 定義在類中,也就是類內部

這時候詭異的事情出現(xiàn)了,擴展函數(shù)無法被調用。接下來看下對應的擴展函數(shù)反編譯后的字節(jié)碼:

public final void exchange(@NotNull List $receiver, int fromIndex, int toIndex) {
            Intrinsics.checkParameterIsNotNull($receiver, "$receiver");         
            int tmp = ((Number)$receiver.get(fromIndex)).intValue();         
            $receiver.set(fromIndex, $receiver.get(toIndex));        
             $receiver.set(toIndex, Integer.valueOf(tmp));     
    }

可以看到失去了static關鍵字并且變成了外部類中的方法(和正常的方法沒什么區(qū)別了),也就是其他地方調用不到了,只有該類或者該類的子類可以調用;如果失去了public關鍵字,那么將只有該類才能使用這個擴展函數(shù),其子類也無法使用。

總結下,如果沒有定義在類中那么該函數(shù)就是靜態(tài)的大家都可以調用。如果定義在類中那么就默認屬于該類和子類的普通函數(shù),所以只有在該類和子類中使用。上面只是說了調用的地方,實際上調用還是需要使用receiver進行調用。

擴展函數(shù)的限制

前面介紹了擴展函數(shù)實現(xiàn)的原理并且看到了擴展函數(shù)的作用域信息,接下來分析下擴展函數(shù)在哪些場景下會被限制。

靜態(tài)擴展函數(shù)

首先來回顧下普通的靜態(tài)函數(shù)/變量如何定義,在Kotlin中使用伴生對象類將函數(shù)/變量定義在其中,那么該函數(shù)/變量就是靜態(tài)函數(shù)/變量了。

class Son {     
    companion object {         
        //該變量為靜態(tài)變量
        val age = 10    
    } 
 }

伴生類的實現(xiàn)可以觀察反編譯后的字節(jié)碼,其是定義了一個Companion的靜態(tài)內部類然后再該類中定義了這些靜態(tài)變量和方法

和普通函數(shù)/變量一樣,擴展函數(shù)也是一樣的定義方式,在伴生對象中定義擴展函數(shù):

fun Son.Companion.foo() {     
    println("age = $age") 
}

這樣foo就不需要Son的實例直接可以通過Son的類名進行調用了。

這樣似乎看起來沒有什么問題,但是當我們需要擴展三方類的靜態(tài)函數(shù)時,如果其沒有用Kotlin的伴生對象指定靜態(tài)方法/變量,那么該方案將無法使用,只能用實例去調用。

函數(shù)優(yōu)先級

有沒有想過這樣一種情況:就是這個類擴展的函數(shù)名之前在這個類中就已經存在了,那么調用這個方法時,會調用擴展函數(shù)還是之前類中定義好的方法。

答案是:之前類中定義的方法、 因此:成員方法優(yōu)先級高于擴展函數(shù)

this的指向

當我們在類中使用擴展函數(shù)時,在擴展函數(shù)體內想要獲取當前類的this,而不是默認的擴展函數(shù)的receivers的類型的時候,我們可以指定this@類名來指向外部類。

擴展函數(shù)注意點

調用者類型是運行時類型,而接受者類型是編譯時類型也就是說當擴展被生命為成員函數(shù)時具體調用哪個類的擴展方法是由它的運行時類型決定,而具體調用哪個擴展方法是根據(jù)其被定義為什么類型也就是編譯時可知類型。

調用者類型也就是上面說的定義在類內部的擴展函數(shù)只有類實例才可以調用,而接受者receiver類型是擴展哪個類的類型

還是java中的規(guī)則: 重載基于編譯時類型,重寫基于運行時類型。

所以在編寫擴展函數(shù)時需要注意

  • 1.如果該擴展函數(shù)定義在類內部就是頂級函數(shù)/成員函數(shù),不能被覆蓋;(因為是基于運行時類型)
  • 2.我們無法訪問其接收器的非公共屬性;(本質是將其變?yōu)榉椒ǖ牡谝粋€參數(shù))
  • 3.擴展接收器總是被靜態(tài)調度。(和重載一樣)
  • 4.也是最重要的一點,不要濫用擴展特性,思考好合適的接受者receivers,不要什么都往context上堆;參數(shù)簡化要考慮是否有副作用

總結

Kotlin的擴展函數(shù)是非常好用的,其符合OOP原則,而且還可以擴展很多函數(shù)Google的ktx庫也是基于這個功能開發(fā)了很多好用的方法。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容