kotlin中擴展屬性和擴展函數

一、kotlin擴展屬性
擴展屬性允許定義在類或者kotlin文件中,不允許定義在函數中。初始化屬性因為屬性沒有后端字段(backing field),所以不允許被初始化,只能由顯式提供的 getter/setter 定義。

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

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

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

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

使用

val testBoldTv = findViewById<TextView>(R.id.tv_test_bold)
testBoldTv.isBold = true
testBoldTv.setText("我是粗體")

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

public final class ExtendsionTextViewKt {
   //get()方法所對應生成靜態函數,并且傳入一個接收者類型對象作為參數
   public static final boolean isBold(@NotNull TextView $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      return $receiver.getPaint().isFakeBoldText();
   }
   //set()方法所對應生成靜態函數,并且傳入一個接收者類型對象作為參數和一個需要set的參數
   public static final void setBold(@NotNull TextView $receiver, boolean value) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      $receiver.getPaint().setFakeBoldText(true);
   }
}

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

ExtendsionTextViewKt.setBold(activity.findViewById(R.id.tv_test_bold), true);

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

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

擴展函數可以在已有類中添加新的方法,不會對原類做修改
擴展函數是靜態解析的
若擴展函數和成員函數一致,則使用該函數時,會優先使用成員函數
擴展一個空對象,在擴展函數內, 可以通過 this 來判斷接收者是否為 NULL,這樣,即使接收者為 NULL,也可以調用擴展函數

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

使用

val testBoldTv = findViewById<TextView>(R.id.tv_test_bold)
testBoldTv.isBoldText()
testBoldTv.setText("我是粗體")

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

//這個類名就是頂層文件名+“Kt”后綴
public final class ExtendsionTextViewKt {
   @NotNull
   public static final TextView isBoldText(@NotNull TextView $receiver) {//擴展函數isBoldText對應實際上是Java中的靜態函數,并且傳入一個接收者類型對象作為參數
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      $receiver.getPaint().setFakeBoldText(true);//設置加粗
      return $receiver;//最后返回這個接收者對象自身,以致于我們在Kotlin中完全可以使用this替代接收者對象或者直接不寫。
   }
}

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

//直接調用靜態函數
ExtendsionTextViewKt.isBoldText(activity.findViewById(R.id.tv_test_bold));

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

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

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

父類ShapeEntity

open class ShapeEntity {
    open fun getShapeType() : String = "ShapeType"
}

子類RectangleEntity

class RectangleEntity : ShapeEntity() {
    //重寫父類方法
    override fun getShapeType() : String = "RectangleType"
}

擴展函數工具類ShapeEt

//擴展函數
fun ShapeEntity.getShapeName() = "ShapeEntity"

fun RectangleEntity.getShapeName() = "RectangleEntity"

工具類ExpandFunUtils

/**
     * 測試擴展函數方法調用
     */
    fun getExtendFunShapeName(s: ShapeEntity) : String {
        return s.getShapeName()
    }

    /**
     * 測試重寫成員方法調用
     */
    fun getExtendFunShapeType() : String {
        val rectangleEntity: ShapeEntity = RectangleEntity()
        return rectangleEntity.getShapeType()
    }

測試方法

var expandFunUtils : ExpandFunUtils ? = ExpandFunUtils()
        val funShapeName = expandFunUtils?.getExtendFunShapeName(RectangleEntity())
        Log.d(TAG, "funShapeName="+funShapeName)

        val funShapeType = expandFunUtils?.getExtendFunShapeType()
        Log.d(TAG, "funShapeType="+funShapeType)

輸出結果:

//輸出父類的名字
funShapeName=ShapeEntity
//輸出子類的類型
funShapeType=RectangleType

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容