Kotlin —— 擴展函數(shù)

一、前言

Kotlin中的擴展函數(shù)特性讓我們的代碼變得更加簡單和整潔。擴展函數(shù)是Kotlin語言中獨有的新特性,利用它可以減少很多的樣板代碼,大大提高開發(fā)的效率;此外擴展函數(shù)的使用也是非常簡單的。我會從以下幾個方面闡述Kotlin中的擴展函數(shù):

  • 為什么要使用Kotlin中的擴展函數(shù)?
  • 怎么去使用擴展函數(shù)和擴展屬性?
  • 什么是擴展函數(shù)和屬性?
  • 擴展函數(shù)和成員函數(shù)區(qū)別
  • 擴展函數(shù)不可以被重寫

二、為什么要使用Kotlin中的擴展函數(shù)?

Koltin可以與Java有非常好的互操作性,所以擴展函數(shù)這個新特性可以很平滑與現(xiàn)有Java代碼集成。甚至純Kotlin的項目都可以基于Java庫,甚至Android中的一些框架庫,第三方庫來構(gòu)建。擴展函數(shù)非常適合Kotlin和Java語言混合開發(fā)模式。在很多公司一些比較穩(wěn)定良好的庫都是Java寫,也完全沒必要去用Kotlin語言重寫。但是想要擴展庫的接口和功能,這時候擴展函數(shù)可能就會派上用場。使用Kotlin的擴展函數(shù)還有一個好處就是沒有副作用,不會對原有庫代碼或功能產(chǎn)生影響。先來看下擴展函數(shù)長啥樣:

// GlobalExtendFunc.kt

fun String.wrap(): String {
    return "前綴-${this}-后綴"
}
val test = "Test".wrap()

Tools -> Kotlin -> Show Kotlin Bytecode,查看轉(zhuǎn)成 java 的代碼:

public final class GlobalExtendFuncKt {
   @NotNull
   private static final String test = wrap("Test");

   @NotNull
   public static final String wrap(@NotNull String $this$wrap) {
      Intrinsics.checkParameterIsNotNull($this$wrap, "$this$wrap");
      return "前綴-" + $this$wrap + "-后綴";
   }

   @NotNull
   public static final String getTest() {
      return test;
   }
}

我們看到:

  • Kotlin會生成一個類,類名:文件名 + Kt;且是 final 修飾;
  • 會生成兩個方法,實際就是 set / get 方法,并且是靜態(tài)常量方法;

如果上面的擴展函數(shù)例子沒有情景,不訪我們換個和 Android 相關(guān)的例子:

// GlobalExtendFunc.kt
fun TextView.isBold() = this.apply {
    paint.isFakeBoldText = true
}
val textView = TextView(null).isBold()

轉(zhuǎn)成 java 后如下:

public final class GlobalExtendFuncKt {
   @NotNull
   private static final TextView textView = isBold(new TextView((Context)null));

   @NotNull
   public static final TextView isBold(@NotNull TextView $this$isBold) {
      Intrinsics.checkParameterIsNotNull($this$isBold, "$this$isBold");
      boolean var2 = false;
      boolean var3 = false;
      int var5 = false;
      TextPaint var10000 = $this$isBold.getPaint();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "paint");
      var10000.setFakeBoldText(true);
      return $this$isBold;
   }

   @NotNull
   public static final TextView getTextView() {
      return textView;
   }
}

三、怎么去使用擴展函數(shù)和擴展屬性

3.1、擴展函數(shù)的基本使用

只需要把擴展的類或者接口名稱,放到即將要添加的函數(shù)名前面。這個類或者名稱就叫做接收者類型,類的名稱與函數(shù)之間用"."調(diào)用連接。this指代的就是接收者對象,它可以訪問擴展的這個類可訪問的方法和屬性。

注意: 接收者類型是由擴展函數(shù)定義的,而接收者對象正是這個接收者類型的對象實例,那么這個對象實例就可以訪問這個類中成員方法和屬性,所以一般會把擴展函數(shù)當(dāng)做成員函數(shù)來用。

3.2、擴展屬性的基本使用

擴展屬性實際上是提供一種方法來訪問屬性而已,并且這些擴展屬性是沒有任何的狀態(tài)的,因為不可能給現(xiàn)有Java庫中的對象額外添加屬性字段,只是使用簡潔語法類似直接操作屬性,實際上還是方法的訪問。
擴展屬性的定義(必需定義 get / set):

var TextView.isBold: Boolean
    get() {
        return this.paint.isFakeBoldText
    }
    set(value) {
        this.paint.isFakeBoldText = true
    }

轉(zhuǎn)成 java 后如下:

public final class GlobalExtendFuncKt {
   public static final boolean isBold(@NotNull TextView $this$isBold) {
      Intrinsics.checkParameterIsNotNull($this$isBold, "$this$isBold");
      TextPaint var10000 = $this$isBold.getPaint();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "this.paint");
      return var10000.isFakeBoldText();
   }

   public static final void setBold(@NotNull TextView $this$isBold, boolean value) {
      Intrinsics.checkParameterIsNotNull($this$isBold, "$this$isBold");
      TextPaint var10000 = $this$isBold.getPaint();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "this.paint");
      var10000.setFakeBoldText(true);
   }
}

注意:

  • 擴展屬性和擴展函數(shù)定義類似,也有接收者類型和接收者對象,接收者對象也是接收者類型的一個實例,一般可以把它當(dāng)做類中成員屬性來使用。
  • 必須定義get()方法,在Kotlin中類中的屬性都是默認添加get()方法的,但是由于擴展屬性并不是給現(xiàn)有庫中的類添加額外的屬性,自然就沒有默認get()方法實現(xiàn)之說。所以必須手動添加get()方法。
  • 由于重寫了set()方法,說明這個屬性訪問權(quán)限是可讀和可寫,需要使用var

四、什么是擴展函數(shù)和屬性

我們從上面例子可以看出,kotlin的擴展函數(shù)真是強大,可以毫無副作用給原有庫的類增加屬性和方法,比如例子中TextView,我們根本沒有去動TextView源碼,但是卻給它增加一個擴展屬性和函數(shù)。具有那么強大功能,到底它背后原理是什么?
上面每個例子,我們都給出了轉(zhuǎn)成 java 后的代碼,一目了然!

4.1、擴展函數(shù)實質(zhì)原理

我們再貼個例子來回顧下:

public final class GlobalExtendFuncKt { // Kotlin文件名 + Kt 組成的類名
   @NotNull
   public static final TextView isBold(@NotNull TextView $this$isBold) {
      // 擴展函數(shù)isBold對應(yīng)實際上是Java中的靜態(tài)函數(shù),并且傳入一個接收者類型對象作為參數(shù)
      // 接收參數(shù)是:$this$函數(shù)名
      Intrinsics.checkParameterIsNotNull($this$isBold, "$this$isBold");
      boolean var2 = false;
      boolean var3 = false;
      int var5 = false;
      TextPaint var10000 = $this$isBold.getPaint();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "paint");
      var10000.setFakeBoldText(true);
      
      // 最后返回這個接收者對象自身,以致于我們在Kotlin中完全可以使用this替代接收者對象或者直接不寫
      return $this$isBold;
   }
}

4.2、Java中調(diào)用Kotlin中定義的擴展函數(shù)

分析完Kotlin中擴展函數(shù)的原理,我們也就很清楚,如何在Java中去調(diào)用Kotlin中定義好的擴展函數(shù)了,實際上使用方法就是靜態(tài)函數(shù)調(diào)用,和我們之前講的頂層函數(shù)在Java中調(diào)用類似,不過唯一不同是需要傳入一個接收者對象參數(shù)。

GlobalExtendFuncKt.isBold(textView)

4.3、擴展屬性的原理

我們在 3.2 中看到,實際就是生成了一個 set / get 方法,這兩方法是靜態(tài)函數(shù)。

4.4、Java中調(diào)用Kotlin中定義的擴展屬性

類似 4.2。

五、擴展函數(shù)和成員函數(shù)區(qū)別

說到擴展函數(shù)和成員函數(shù)的區(qū)別,通過上面例子我們已經(jīng)很清楚了,這里做個歸納總結(jié):

  • 擴展函數(shù)和成員函數(shù)使用方式類似,可以直接訪問被擴展類的方法和屬性;
    (傳入了一個擴展類的對象,內(nèi)部實際上是用實例對象去訪問擴展類的方法和屬性)
  • 擴展函數(shù)不能打破擴展類的封裝性,不能像成員函數(shù)一樣直接訪問內(nèi)部私有函數(shù)和屬性;
    (擴展函數(shù)訪問實際是類的對象訪問,由于類的對象實例不能訪問內(nèi)部私有函數(shù)和屬性,自然擴展函數(shù)也就不能訪問內(nèi)部私有函數(shù)和屬性了)
  • 擴展函數(shù)實際上是一個靜態(tài)函數(shù)是處于類的外部,而成員函數(shù)則是類的內(nèi)部函數(shù);
  • 父類成員函數(shù)可以被子類重寫,而擴展函數(shù)則不行;

六、擴展函數(shù)不可以被重寫

在Kotlin和Java中我們都知道類的成員函數(shù)是可以被重寫的,子類是可以重寫父類的成員函數(shù),但是子類是不可以重寫父類的擴展函數(shù)。

open class Fruit {
    open fun name() = "This is Fruit class"
}
class Apple : Fruit() {
    // 重寫父類方法
    override fun name() = "This is Apple class"
}

// 父類和子類的擴展函數(shù)
fun Fruit.type() = "typeof Fruit"
fun Apple.type() = "typeof Apple"

fun main(args: Array<String>) {
    val fruit: Fruit = Apple()
    println("成員函數(shù)測試: ${fruit.name()}")
    println("擴展函數(shù)測試: ${fruit.type()}")
}

// 打印結(jié)果:
// 成員函數(shù)測試: This is Apple class
// 擴展函數(shù)測試: typeof Fruit

以上運行結(jié)果再次說明了擴展函數(shù)并不是類的一部分,它是聲明與類外部的,盡管子類和父類擁有了相同的擴展函數(shù),但是實際上擴展函數(shù)是靜態(tài)函數(shù)。從編譯內(nèi)部來看,子類和父類擁有了相同的擴展函數(shù),實際上就是定義兩個同名的靜態(tài)擴展函數(shù)分別傳入父類對象和子類對象,那么調(diào)用的方法肯定也是父類中的方法和子類中的方法,所以輸出肯定是父類的。

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容