Kotlin實戰--給別人的類添加方法:擴展函數和屬性

1.概述

擴展函數能夠讓我們與項目無縫接入,我們能夠在無侵入的情況下爲類添加更多的行爲和屬性,Kotlin庫中很多地方就用到了擴展函數,如Anko庫裏面的佈局系統,集合的使用等。正是由於擴展函數的使用,纔可以讓我們與JAVA無縫銜接。並且擴展函數的使用非常的簡單,下面我將從下面幾個方面介紹擴展函數。

  1. 爲什麼要使用Kotlin中的擴展函數?
  2. 如何使用擴展函數和擴展屬性?
  3. 擴展函數和屬性原理
  4. 擴展函數和成員函數區別
  5. 擴展函數不可以被重寫

2.爲什麼要使用Kotlin中的擴展函數

我們都知道在Koltin這門語言可以與Java有非常好的互操作性,所以擴展函數這個新特性可以很平滑與現有Java代碼集成。甚至純Kotlin的項目都可以基於Java庫,甚至Android中的一些框架庫,第三方庫來構建。擴展函數非常適合Kotlin和Java語言混合開發模式。在很多公司一些比較穩定良好的庫都是Java寫,也完全沒必要去用Kotlin語言重寫。但是想要擴展庫的接口和功能,這時候擴展函數可能就會派上用場。使用Kotlin的擴展函數還有一個好處就是沒有副作用,不會對原有庫代碼或功能產生影響。

3.如何使用擴展函數和擴展屬性

下面我將具體介紹擴展函數擴展屬性

3.1擴展函數的基本使用

下面我們以一個例子來引入

給TextView加粗

//添加擴展函數
fun TextView.setBold() = this.apply {
    this.paint.isFakeBoldText = true
}

//繼承TextView
open class TextViewExtension(context: Context?, attrs: AttributeSet?) : TextView(context, attrs) {

}

調用

textView.setBold()  //textView爲TextView的實例,不一定爲TextViewExtension的實例。

大家是不是覺得奇怪,在類外面定義行爲(Java中屬性和行爲都屬於類,定義在類裏面),這裏大家可以看下Kotlin語法糖–頂層函數

你所有要做的,就是把你要擴展的類或者接口的名稱,放到即將添加的函數前面。這個類的名稱被稱爲接收者類型;用來調用這個擴展函數的那個對象,叫作接收者對象,如下圖

在這裏插入圖片描述

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

3.2擴展屬性的基本使用

擴展屬性實際上是提供一種方法來訪問屬性而已,並且這些擴展屬性是沒有任何的狀態的,因爲不可能給現有Java庫中的對象額外添加屬性字段,只是使用簡潔語法類似直接操作屬性,實際上還是方法的訪問。

//添加擴展屬性
var TextView.textSizeS: Float
    get() {//必須定義get()方法,因爲不是類的成員變量,也自然就沒有了默認的get()實現
        return paint.textSize
    }
    set(value) {
        paint.textSize = value
    }
    
//繼承TextView
open class TextViewExtension(context: Context?, attrs: AttributeSet?) : TextView(context, attrs) {

}

調用

textView.textSizeS=60F // 這裏textSize是TextView默認方法,不能一樣,否則以成員函數爲主。並且這裏單位是px,成員函數單位是sp。

總結:

  1. 擴展屬性和擴展函數定義類似,也有接收者類型和接收者對象,接收者對象也是接收者類型的一個實例,一般可以把它當做類中成員屬性來使用。

  2. 必須定義get()方法,在Kotlin中類中的屬性都是默認添加get()方法的,但是由於擴展屬性並不是給現有庫中的類添加額外的屬性,自然就沒有默認get()方法實現之說。所以必須手動添加get()方法。

  3. 由於重寫了set()方法,說明這個屬性訪問權限是可讀和可寫,需要使用var

4.擴展函數和屬性原理

我們從上面例子可以看出,kotlin的擴展函數真是強大,可以毫無副作用給原有庫的類增加屬性和方法,比如例子中TextView,我們根本沒有去動TextView源碼,但是卻給它增加一個擴展屬性和函數。具有那麼強大功能,到底它背後原理是什麼?其實很簡單,通過decompile看下反編譯後對應的Java代碼就一目瞭然了。

4.1擴展函數原理

擴展函數實際上就是一個對應Java中的靜態函數,這個靜態函數參數爲接收者類型的對象,然後利用這個對象就可以訪問這個類中的成員屬性和方法了,並且最後返回一個這個接收者類型對象本身。這樣在外部感覺和使用類的成員函數是一樣的。

public final class TextViewExtensionKt {
   @NotNull
   public static final TextView setBold(@NotNull TextView $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "receiver$0");
      int var3 = false;
      TextPaint var10000 = $receiver.getPaint();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "this.paint");
      var10000.setFakeBoldText(true);
      return $receiver;
   }
}

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

TextViewExtensionKt.setBold(findViewById(R.id.tv_text))

4.2擴展屬性原理

擴展屬性實際上就是提供某個屬性訪問的set,get方法,這兩個set,get方法是靜態函數,同時都會傳入一個接收者類型的對象,然後在其內部用這個對象實例去訪問和修改對象所對應的類的屬性。

public final class TextViewExtensionKt {
   public static final float getTextSizeS(@NotNull TextView $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "receiver$0");
      TextPaint var10000 = $receiver.getPaint();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "paint");
      return var10000.getTextSize();
   }

   public static final void setTextSizeS(@NotNull TextView $receiver, float value) {
      Intrinsics.checkParameterIsNotNull($receiver, "receiver$0");
      TextPaint var10000 = $receiver.getPaint();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "paint");
      var10000.setTextSize(value);
   }
}

Java中調用Kotlin中定義的擴展屬性:

Java調用Kotlin中定義的擴展屬性也很簡單,就相當於直接調用生成的set(),get()方法一樣。

 TextViewExtensionKt.setTextSizeS(findViewById(R.id.tv_text),40);

5.擴展函數和成員函數區別

擴展函數和成員函數的區別,通過上面例子我們已經很清楚了,這裏做個歸納總結:

  1. 擴展函數和成員函數使用方式類似,可以直接訪問被擴展類的方法和屬性。(原理: 傳入了一個擴展類的對象,內部實際上是用實例對象去訪問擴展類的方法和屬性)
  2. 擴展函數不能打破擴展類的封裝性,不能像成員函數一樣直接訪問內部私有函數和屬性。(原理: 原理很簡單,擴展函數訪問實際是類的對象訪問,由於類的對象實例不能訪問內部私有函數和屬性,自然擴展函數也就不能訪問內部私有函數和屬性了)
  3. 擴展函數實際上是一個靜態函數是處於類的外部,而成員函數則是類的內部函數。
  4. 父類成員函數可以被子類重寫,而擴展函數則不行

6.擴展函數不可以被重寫

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

open class Animal {
    open fun shout() = println("animal is shout")//定義成員函數
}

class Cat: Animal() {
    override fun shout() {
        println("Cat is shout")//子類重寫父類成員函數
    }
}

//定義子類和父類擴展函數
fun Animal.eat() = println("Animal eat something")

fun Cat.eat()= println("Cat eat fish")

//測試
fun main(args: Array<String>) {
    val animal: Animal = Cat()
    println("成員函數測試: ${animal.shout()}")
    println("擴展函數測試: ${animal.eat()}")
}

運行結果:

在這裏插入圖片描述

以上運行結果再次說明了擴展函數並不是類的一部分,它是聲明與類外部的,儘管子類和父類擁有了相同的擴展函數,但是實際上擴展函數是靜態函數。從編譯內部來看,子類和父類擁有了相同的擴展函數,實際上就是定義兩個同名的靜態擴展函數分別傳入父類對象和子類對象,那麼調用的方法肯定也是父類中的方法和子類中的方法,所以輸出肯定是父類的。

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