302 kotlin的擴(kuò)展函數(shù)和擴(kuò)展屬性

kotlin擴(kuò)展方法、屬性

1.概念

kotlin支持在不修改類代碼的情況下,動態(tài)為類添加屬性(擴(kuò)展屬性)和方法(擴(kuò)展方法)。

2.擴(kuò)展方法

擴(kuò)展方法執(zhí)行靜態(tài)解析(編譯時),成員方法執(zhí)行動態(tài)解析(運行時)

(1)語法格式

? 定義一個函數(shù),在被定義的函數(shù)前面添加“類名.”,該函數(shù)即為該類名對應(yīng)類的拓展方法。

fun main(args: Array<String>) {
    val extensionClass = ExtensionClass()
    //調(diào)用拓展方法
    extensionClass.test()
}
//定義一個空類
class ExtensionClass
//為該空類定義一個拓展方法test()方法
fun ExtensionClass.test() = println("我是ExtensionClass的拓展方法")

(2)成員方法優(yōu)先

如果被擴(kuò)展的類的擴(kuò)展方法與該類的成員方法名字和參數(shù)一樣,該類對象調(diào)用該方法時,調(diào)用的會是成員方法。

fun main(args: Array<String>) {
    val extension = ExtensionTest()
    //此處調(diào)用的會是成員方法
    extension.test()
}

class ExtensionTest {
    fun test() = print("成員方法")
}
//該方法不會被調(diào)用
fun ExtensionTest.test() = println("擴(kuò)展方法")

(3)為系統(tǒng)類添加拓展方法(以String為例)

fun main(args: Array<String>) {
    val str = "123456"
    //調(diào)用String的拓展方法
    println(str.lastIndex())
}
//為String定義一個拓展方法
fun String.lastIndex() = length - 1

(4)擴(kuò)展實現(xiàn)原理

java是一門靜態(tài)語言,無法動態(tài)的為類添加方法、屬性,除非修改類的源碼,并重新編譯該類。

? kotlin擴(kuò)展屬性、方法時看起來是為該類動態(tài)添加了成員,實際上并沒有真正修改這個被擴(kuò)展的類,kotlin實質(zhì)是定義了一個函數(shù),當(dāng)被擴(kuò)展的類的對象調(diào)用擴(kuò)展方法時,kotlin會執(zhí)行靜態(tài)解析,將調(diào)用擴(kuò)展函數(shù)靜態(tài)解析為函數(shù)調(diào)用。

靜態(tài)解析:根據(jù)調(diào)用對象、方法名找到拓展函數(shù),轉(zhuǎn)換為函數(shù)調(diào)用。

如(2)str.lastIndex()方法執(zhí)行的過程為:
①檢查str類型(發(fā)現(xiàn)為String類型);

? ②檢查String是否定義了lastIndex()成員方法,如果定義了,編譯直接通過;

? ③如果String沒定義lastIndex()方法,kotlin開始查找程序是否有為String類擴(kuò)展了lastIndex()方法(即是否有fun String.lastIndex()),如果有定義該擴(kuò)展方法,會執(zhí)行該擴(kuò)展方法;

? ④既沒定義lastIndex()成員方法也沒定義擴(kuò)展方法,編譯自然不通過。

(5)靜態(tài)解析調(diào)用擴(kuò)展方法注意點

由于靜態(tài)調(diào)用擴(kuò)展方法是在編譯時執(zhí)行,因此,如果父類和子類都擴(kuò)展了同名的一個擴(kuò)展方法,引用類型均為父類的情況下,會調(diào)用父類的擴(kuò)展方法。

/**
 * 拓展屬性、方法
 */
fun main(args: Array<String>) {
    val father : ExtensionTest = ExtensionTest()
    father.test()//調(diào)用父類擴(kuò)展方法
    val child1 : ExtensionTest = ExtensionSubTest()
    child1.test()//引用類型為父類類型,編譯時靜態(tài)調(diào)用的還是父類的擴(kuò)展方法
    val child2 : ExtensionSubTest = ExtensionSubTest()
    child2.test()//此時才是調(diào)用子類的擴(kuò)展方法
}

/**
 * 父類
 */
open class ExtensionTest

/**
 * 子類
 */
class ExtensionSubTest : ExtensionTest()

/**
 * 父類擴(kuò)展一個test方法
 */
fun ExtensionTest.test() = println("父類擴(kuò)展方法")

/**
 * 子類擴(kuò)展一個test方法
 */
fun ExtensionSubTest.test() = println("子類擴(kuò)展方法")

(6)可空類型擴(kuò)展方法(以擴(kuò)展equals方法為例)

kotlin允許擴(kuò)展可空類型擴(kuò)展方法,這樣,null也能調(diào)用該方法。

fun main(args: Array<String>) {
    val a: Any? = null
    val b: Any? = null
    println(a.equals(b))
}

fun Any?.equals(any: Any?): Boolean = this != null && any != null && any.equals(this)

3.擴(kuò)展屬性

(1)概念

kotlin允許動態(tài)為類擴(kuò)展屬性,擴(kuò)展屬性是通過添加get、set方法實現(xiàn),沒有幕后字段(filed)。

? 擴(kuò)展屬性也沒有真的為該類添加了屬性,只能說是為該類通過get、set方法計算出屬性。

? 限制:①擴(kuò)展屬性不能有初始值;②擴(kuò)展屬性不能用filed關(guān)鍵字訪問幕后字段;③val必須提供get方法,var必須提供get和set方法。

(2)定義擴(kuò)展屬性

fun main(args: Array<String>) {
    val extensionTest = ExtensionTest("a", "b")
    println(extensionTest.param1)//a
    println(extensionTest.param2)//b
    println(extensionTest.extensionParam)//a-b
}

/**
 * 定義一個類,包含屬性param1、屬性param2
 */
class ExtensionTest(var param1: String, var param2: String)

/**
 * 為該類擴(kuò)展屬性extensionParam
 */
var ExtensionTest.extensionParam: String
    set(value) {
        param1 = "param1$value"
        param1 = "param2$value"
    }
    get() = "$param1-$param2"

4.以類成員方式定義擴(kuò)展

在某個類里面為其他類定義擴(kuò)展方法、屬性,該擴(kuò)展的方法,只能在該類中通過被擴(kuò)展的類的對象調(diào)用擴(kuò)展方法。

? 以類成員方式定義的擴(kuò)展,屬于被擴(kuò)展的類,因此在擴(kuò)展方法直接調(diào)用被擴(kuò)展的類的成員(this可以省略),同時因為它位于所在類中,因此又可以直接調(diào)用所在類的成員。

fun main(args: Array<String>) {
    val extensionTest = ExtensionTest()
    val extensionTest2 = ExtensionTest2()
    extensionTest2.info(extensionTest)
}

/**
 * 定義一個類包含test方法
 */
class ExtensionTest {
    fun test() = println("ExtensionTest的test方法")
}

/**
 * 定義一個類包含test方法,包含ExtensionTest的一個擴(kuò)展方法
 */
class ExtensionTest2 {
    val a = "a"
    fun test() = println("ExtensionTest2的test方法")
    fun ExtensionTest.func() {
        println(a)//調(diào)用擴(kuò)展類的成員
        test()//調(diào)用被擴(kuò)展類的成員,相當(dāng)于this.test()
        this@ExtensionTest2.test()//同名的需要用this@類名的方式來調(diào)用
    }

    fun info(extensionTest: ExtensionTest) {
        extensionTest.func()
    }
}

5.帶接收者的匿名擴(kuò)展函數(shù)

(1)概念

擴(kuò)展方法(fun 類名.方法名())去掉方法名就是所謂的帶接收者的匿名擴(kuò)展函數(shù),接收者就是類本身,形如:fun Int.() : Int。

fun main(args: Array<String>) {
    val extensionTest = ExtensionTest()
    println(extensionTest.noNameExtensionFun("向帶接收者的匿名函數(shù)傳入的參數(shù)"))//使用匿名擴(kuò)展函數(shù)
}

/**
 * 定義一個空類
 */
class ExtensionTest

/**
 * 為空類定義一個帶接收者的匿名擴(kuò)展函數(shù)
 */
var noNameExtensionFun = fun ExtensionTest.(param: String): String {
    println(param)
    return "我是來自帶接收者的匿名擴(kuò)展函數(shù)的返回值"
}

(2)帶接收者的匿名擴(kuò)展函數(shù)的函數(shù)類型

? 與普通函數(shù)一樣,匿名擴(kuò)展方法也有函數(shù)類型,(1)中的函數(shù)類型為:ExtensionTest.(String) -> String

(3)帶接收者的匿名擴(kuò)展函數(shù)與lambda表達(dá)式

如果能根據(jù)上下文推斷出接收者類型,則可以使用lambda表達(dá)式

fun main(args: Array<String>) {
    test {
        println(it)
        "匿名擴(kuò)展函數(shù)返回值"
    }
}

/**
 * 定義一個空類
 */
class ExtensionTest

/**
 * 定義一個函數(shù),形參為ExtensionTest.(String) -> String類型,相當(dāng)于同時為ExtensionTest類擴(kuò)展了一個匿名擴(kuò)展函數(shù)
 */
fun test(fn: ExtensionTest.(String) -> String) {
    val extensionTest = ExtensionTest()
    println("調(diào)用匿名擴(kuò)展函數(shù):${extensionTest.fn("匿名擴(kuò)展函數(shù)傳入形參")}")
}

6.擴(kuò)展使用場景

擴(kuò)展極大的增加了程序的靈活性,java如果想對一個類擴(kuò)展某些屬性,必須通過繼承的方式等實現(xiàn),kotlin使用語法直接可以動態(tài)的擴(kuò)展,能更方便組織一些工具方法等。

fun main(args: Array<String>) {
    "打印日志".log()
}

/**
 * 為String字符串添加一個打印日志的擴(kuò)展方法
 */
fun String.log() {
    println(this)
}

擴(kuò)展函數(shù)和擴(kuò)展屬性的實現(xiàn)

我們都知道,Java 中,只有一個類型的成員屬性和成員方法才能用“對象.屬性 / 方法()”的方式調(diào)用,一個類型的對象是絕對不可能通過這種方法調(diào)用其他類里定義的方法(除非存在繼承或?qū)崿F(xiàn)關(guān)系)。而 Kotlin 提供的擴(kuò)展函數(shù)和擴(kuò)展屬性打破了這一規(guī)則,它是怎么實現(xiàn)的呢?

首先看例子:

// Test.kt
fun <T> MutableList<T>.swap(indexA: Int, indexB: Int) {
    val temp = this[indexA]
    this[indexA] = this[indexB]
    this[indexB] = temp
}

val Int.isOdd: Boolean
    get() = this and 1 == 1

我們在這里定義一個擴(kuò)展函數(shù) swap,它的接收者是 MutableList,作用是調(diào)換傳入的兩個索引對應(yīng)的值。然后給 Int 類定義了一個擴(kuò)展屬性 isOdd,用來檢查這個 Int 是不是奇數(shù)。

這時我們就可以這樣調(diào)用它們了:

val list = mutableListOf(1, 2, 3)
list.swap(0, 1)
println(list)
// [2, 1, 3]

val n = 3
println(n.isOdd)
// true

但想要在 Java 中調(diào)用它們,就要這么寫了:

// import TestKt
List<Integer> list = new ArrayList<>();
list.add(1); list.add(2); list.add(3);
TestKt.swap(list, 0, 1);
System.out.println(list);

int n = 3;
println(TestKt.isOdd(n));

實際上,所有的擴(kuò)展函數(shù)和擴(kuò)展屬性都會被編譯成一個方法,這個方法的第一個參數(shù)就是擴(kuò)展的接收者,然后才是其它各個參數(shù)。對于擴(kuò)展屬性來說 ,因為編譯后這個屬性并不存在,所以不能像一般的類屬性那樣對它進(jìn)行初始化,而是要自定義 getter 和 setter 來訪問它。

為什么要用擴(kuò)展函數(shù)和擴(kuò)展屬性

Java 里有許多工具類,比如 Collections、Arrays、Objects 等等,它們提供了一系列靜態(tài)方法來充當(dāng)工具函數(shù),通過參數(shù)傳入被操作的對象,既不直觀又冗長無比。

比如對于 Integer.parseInt(String s),Kotlin 就用一個擴(kuò)展函數(shù)替代了它:

inline fun String.toInt() = java.lang.Integer.parseInt(this)

雖然還是調(diào)用這個方法,但這樣定義有兩個好處,一是減少了代碼量,二是形成了一個統(tǒng)一的標(biāo)準(zhǔn),所有其他基本類型都可以重載這個方法,實現(xiàn)同一個行為。

從另一個角度來看,Kotlin 鼓勵開發(fā)者 盡量精簡類的定義,一個類只定義框架,工具函數(shù)可以通過外部擴(kuò)展一點點地添加,盡量不改動原有的類。

kotlin的擴(kuò)展函數(shù)和擴(kuò)展屬性

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

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

一、為什么要使用Kotlin中的擴(kuò)展函數(shù)

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

  • 給TextView設(shè)置加粗簡單的例子
//擴(kuò)展函數(shù)定義
fun TextView.isBold() = this.apply { 
    paint.isFakeBoldText = true
}

//擴(kuò)展函數(shù)調(diào)用
activity.find<TextView>(R.id.course_comment_tv_score).isBold()

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

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

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

  • 2、擴(kuò)展屬性的基本使用
    擴(kuò)展屬性實際上是提供一種方法來訪問屬性而已,并且這些擴(kuò)展屬性是沒有任何的狀態(tài)的,因為不可能給現(xiàn)有Java庫中的對象額外添加屬性字段,只是使用簡潔語法類似直接操作屬性,實際上還是方法的訪問。
//擴(kuò)展屬性定義
var TextView.isBolder: Boolean
    get() {//必須定義get()方法,因為不能在現(xiàn)有對象添加字段,也自然就沒有了默認(rèn)的get()實現(xiàn)
        return this.paint.isFakeBoldText
    }
    set(value) {
        this.paint.isFakeBoldText = true
    }
//擴(kuò)展屬性調(diào)用
activity.find<TextView>(R.id.course_comment_tv_score).isBolder = true

注意:

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

三、什么是擴(kuò)展函數(shù)和屬性

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

  • 1、擴(kuò)展函數(shù)實質(zhì)原理
    擴(kuò)展函數(shù)實際上就是一個對應(yīng)Java中的靜態(tài)函數(shù),這個靜態(tài)函數(shù)參數(shù)為接收者類型的對象,然后利用這個對象就可以訪問這個類中的成員屬性和方法了,并且最后返回一個這個接收者類型對象本身。這樣在外部感覺和使用類的成員函數(shù)是一樣的。
public final class ExtendsionTextViewKt {//這個類名就是頂層文件名+“Kt”后綴,這個知識上篇博客有詳細(xì)介紹
   @NotNull
   public static final TextView isBold(@NotNull TextView $receiver) {//擴(kuò)展函數(shù)isBold對應(yīng)實際上是Java中的靜態(tài)函數(shù),并且傳入一個接收者類型對象作為參數(shù)
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      $receiver.getPaint().setFakeBoldText(true);//設(shè)置加粗
      return $receiver;//最后返回這個接收者對象自身,以致于我們在Kotlin中完全可以使用this替代接收者對象或者直接不寫。
   }
}

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

  • 3、擴(kuò)展屬性實質(zhì)原理
    擴(kuò)展屬性實際上就是提供某個屬性訪問的set,get方法,這兩個set,get方法是靜態(tài)函數(shù),同時都會傳入一個接收者類型的對象,然后在其內(nèi)部用這個對象實例去訪問和修改對象所對應(yīng)的類的屬性。
public final class ExtendsionTextViewKt {
   //get()方法所對應(yīng)生成靜態(tài)函數(shù),并且傳入一個接收者類型對象作為參數(shù)
   public static final boolean isBolder(@NotNull TextView $receiver) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      return $receiver.getPaint().isFakeBoldText();
   }
   //set()方法所對應(yīng)生成靜態(tài)函數(shù),并且傳入一個接收者類型對象作為參數(shù)和一個需要set的參數(shù)
   public static final void setBolder(@NotNull TextView $receiver, boolean value) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      $receiver.getPaint().setFakeBoldText(true);
   }
}

  • 4、Java中調(diào)用Kotlin中定義的擴(kuò)展屬性
    Java調(diào)用Kotlin中定義的擴(kuò)展屬性也很簡單,就相當(dāng)于直接調(diào)用生成的set(),get()方法一樣。
    ExtendsionTextViewKt.setBolder(activity.findViewById(R.id.course_comment_tv_score), true);

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

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

  • 1、擴(kuò)展函數(shù)和成員函數(shù)使用方式類似,可以直接訪問被擴(kuò)展類的方法和屬性。(原理: 傳入了一個擴(kuò)展類的對象,內(nèi)部實際上是用實例對象去訪問擴(kuò)展類的方法和屬性)

  • 2、擴(kuò)展函數(shù)不能打破擴(kuò)展類的封裝性,不能像成員函數(shù)一樣直接訪問內(nèi)部私有函數(shù)和屬性。(原理: 原理很簡單,擴(kuò)展函數(shù)訪問實際是類的對象訪問,由于類的對象實例不能訪問內(nèi)部私有函數(shù)和屬性,自然擴(kuò)展函數(shù)也就不能訪問內(nèi)部私有函數(shù)和屬性了)

  • 3、擴(kuò)展函數(shù)實際上是一個靜態(tài)函數(shù)是處于類的外部,而成員函數(shù)則是類的內(nèi)部函數(shù)。

  • 父類成員函數(shù)可以被子類重寫,而擴(kuò)展函數(shù)則不行

五、擴(kuò)展函數(shù)不可以被重寫

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

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

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

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

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

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


最后編輯于
?著作權(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ù)。

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