Kotlin 特性之?dāng)U展函數(shù)

什么是擴(kuò)展函數(shù)

擴(kuò)展函數(shù)是 Java 不具備的,而 Kotlin 獨(dú)有的特性,在日常開發(fā)中使用頻率很高,類似于設(shè)計(jì)模式中的裝飾模式,其作用就是在不改變?cè)蓄惖那闆r下,擴(kuò)展新的功能。

如何使用擴(kuò)展函數(shù)和擴(kuò)展屬性

擴(kuò)展函數(shù)的基本使用

在需要被擴(kuò)展的類或者接口后面添加方法即可,下面是一個(gè)給 Int 類型擴(kuò)展了一個(gè)判斷是否是偶數(shù)的方法的例子,在擴(kuò)展函數(shù)里使用 this 指代被擴(kuò)展的類或者接口的實(shí)例對(duì)象:

/**
 * 是否是偶數(shù),用與運(yùn)算取出最后一位,最后一位為0則是偶數(shù)
 */
fun Int.isEven(): Boolean {
    return (this and 1) == 0
}

使用的時(shí)候,對(duì)于任意一個(gè) Int 型數(shù)據(jù)都可以直接調(diào)用 isEven() 方法。

2.isEven() //得到返回值true

擴(kuò)展屬性的基本使用

擴(kuò)展屬性實(shí)際上是提供一種方法來訪問屬性而已,并且這些擴(kuò)展屬性是沒有任何的狀態(tài)的,因?yàn)椴豢赡芙o被擴(kuò)展的類或者接口的對(duì)象額外添加屬性字段,只是使用簡(jiǎn)潔語(yǔ)法類似直接操作屬性,實(shí)際上還是方法的訪問。例如給 TextView 擴(kuò)展一個(gè)屬性表示是否是粗體:

//擴(kuò)展屬性定義
var TextView.isBolder: Boolean
    get() {
        return this.paint.isFakeBoldText
    }
    set(value) {
        this.paint.isFakeBoldText = value
    }

在擴(kuò)展屬性中也是使用 this 指代被擴(kuò)展的類或者接口的實(shí)例對(duì)象。另外必須定義 get() 方法,在 Kotlin 中類中的屬性都是默認(rèn)添加 get() 方法的,但是由于擴(kuò)展屬性并不是給現(xiàn)有類添加額外的屬性,自然就沒有默認(rèn) get() 方法實(shí)現(xiàn)了,所以必須手動(dòng)添加 get() 方法。至于 set() 方法,就看屬性是否可變了,也就是該擴(kuò)展屬性對(duì)應(yīng)是 var 還是 val 了。

什么是頂層函數(shù)

在解析擴(kuò)展函數(shù)的本質(zhì)之前,先來了解一下 Kotlin 的另一個(gè)特性 -- 頂層函數(shù)。

在 Java 中有靜態(tài)方法和靜態(tài)屬性,一般是為了提供全局共享訪問的方法和屬性,是獨(dú)立于對(duì)象之外的。靜態(tài)方法和靜態(tài)屬性需要在類中聲明,在使用的時(shí)候也是通過 類名.方法名 或者 類名.屬性名 的方式訪問。靜態(tài)函數(shù)內(nèi)部是不包含狀態(tài)的,也就是所謂的純函數(shù),它的輸入僅僅來自于它的參數(shù)列表,而它的輸出也僅僅依賴于它參數(shù)列表,靜態(tài)函數(shù)所在的類只是作為一個(gè)容器的角色。

在 Kotlin 中認(rèn)為一個(gè)函數(shù)有時(shí)候并不需要屬于任何一個(gè)類,它可以獨(dú)立存在。所以在 Kotlin 中類似靜態(tài)函數(shù)和靜態(tài)屬性可以去掉外層類的容器,一個(gè)函數(shù)或者屬性可以直接定義在一個(gè) Kotlin 文件的頂層中,在使用的地方只需要 import 這個(gè)函數(shù)或?qū)傩约纯桑@就是頂層函數(shù)。

頂層函數(shù)的使用

首先,創(chuàng)建一個(gè)文件,這里文件取名為 TopFunction:


創(chuàng)建文件.png

然后在文件中定義函數(shù),這里定義了一個(gè)判斷 Int 數(shù)字是否是偶數(shù)的函數(shù):

/**
 * 是否是偶數(shù),用與運(yùn)算取出最后一位,最后一位為0則是偶數(shù)
 */
fun isEven(num: Int): Boolean {
    return (num and 1) == 0
}

需要使用的時(shí)候直接調(diào)用函數(shù)即可:

isEven(2) //得到返回值true

定義并使用一個(gè)頂層函數(shù)很簡(jiǎn)單,但是頂層函數(shù)究竟是怎么運(yùn)行在 JVM 中的呢?如果是 Java 和 Kotlin 混合開發(fā)模式,在 Kotlin 中定義的頂層函數(shù),在 Java 中又是怎么調(diào)用的呢?

頂層函數(shù)的本質(zhì)

通過 decompile 看下反編譯后對(duì)應(yīng)的 Java 代碼:

public final class TopFunctionKt {
   public static final boolean isEven(int num) {
      return (num & 1) == 0;
   }
}
  • 頂層文件會(huì)反編譯成一個(gè)容器類。(類名默認(rèn)為頂層文件名+"Kt"后綴)
  • 頂層函數(shù)會(huì)反編譯成一個(gè) static 靜態(tài)函數(shù)。

所以頂層函數(shù)本質(zhì)就是 Java 中的靜態(tài)函數(shù)。如果需要在 Java 中調(diào)用 Kotlin 中的頂層函數(shù),方式很簡(jiǎn)單,就是利用反編譯生成的類作為靜態(tài)函數(shù)容器類直接調(diào)用對(duì)應(yīng)的函數(shù),例如上面的頂層函數(shù)在 Java 中調(diào)用寫法,TopFunctionKt.isEven(int num)。

注意: 頂層文件反編譯成的 Java 中的容器類名默認(rèn)是頂層文件名+“Kt”后綴,但是也是可以自定義的。也就是說頂層文件名和生成容器類名沒有必然的聯(lián)系。通過 Kotlin 中的 @file: JvmName("自定義生成類名") 注解就可以自動(dòng)生成對(duì)應(yīng) Java 調(diào)用類名,注意需要放在文件頂部,在package聲明的前面。這樣 Java 調(diào)用自定義類名頂層函數(shù)就更加自然,一般建議使用注解修改類名。

擴(kuò)展函數(shù)的本質(zhì)

擴(kuò)展函數(shù)也是不屬于任何一個(gè)類,獨(dú)立存在于 Kotlin 文件中,是不是和頂層函數(shù)一樣,本質(zhì)是 Java 中的靜態(tài)函數(shù)呢?答案是肯定的。

/**
 * 是否是偶數(shù),用與運(yùn)算取出最后一位,最后一位為0則是偶數(shù)
 */
fun Int.isEven(): Boolean {
    return (this and 1) == 0
}

我們將上述擴(kuò)展函數(shù)寫在文件名為 SpreadFunction 的文件中,通過 decompile 看下反編譯后對(duì)應(yīng)的 Java 代碼:

public final class SpreadFunctionKt {
   public static final boolean isEven(int $this$isEven) {
      return ($this$isEven & 1) == 0;
   }
}

跟頂層函數(shù)如出一轍,擴(kuò)展函數(shù)本質(zhì)就是 Java 中的靜態(tài)函數(shù),被擴(kuò)展的類或者接口的實(shí)例對(duì)象會(huì)作為函數(shù)參數(shù),用 this 指代。同樣的,在 Java 中調(diào)用 Kotlin 中的擴(kuò)展函數(shù)也是一樣的方式,不同的地方是需要插入一個(gè)被擴(kuò)展的類或者接口的實(shí)例對(duì)象。

擴(kuò)展屬性實(shí)際上就是提供某個(gè)屬性訪問的set,get方法,set,get方法本質(zhì)都是靜態(tài)函數(shù),同時(shí)都會(huì)傳入一個(gè)被擴(kuò)展類的對(duì)象,然后在其內(nèi)部用這個(gè)實(shí)例對(duì)象去訪問和修改對(duì)象所對(duì)應(yīng)的類的屬性,在此就不細(xì)說了。

擴(kuò)展函數(shù)與成員函數(shù)

  1. 擴(kuò)展函數(shù)和成員函數(shù)都可以訪問被擴(kuò)展類的公有方法和屬性。
  2. 擴(kuò)展函數(shù)不能訪問被擴(kuò)展類的私有方法和屬性,成員函數(shù)可以訪問類中的私有方法和屬性。
  3. 父類成員函數(shù)可以被子類重寫,但擴(kuò)展函數(shù)不可以被子類重寫。

理解了擴(kuò)展函數(shù)的本質(zhì)是一個(gè)靜態(tài)函數(shù),且處于類的外部,被擴(kuò)展類的實(shí)例對(duì)象作為函數(shù)參數(shù)傳入,就可以很好地理解擴(kuò)展函數(shù)與成員函數(shù)之間的共同性和差異性了。擴(kuò)展函數(shù)通過實(shí)例對(duì)象進(jìn)行訪問自然不能訪問類中的私有方法和屬性,但可以訪問類中的公有方法和屬性。擴(kuò)展函數(shù)處于類的外部,并不是類的一部分,不會(huì)被子類所繼承,自然不可以被子類重寫。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容