kotlin擴展方法、屬性


1.概念

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

2.擴展方法

​ 擴展方法執行靜態解析(編譯時),成員方法執行動態解析(運行時)。

(1)語法格式

​ 定義一個函數,在被定義的函數前面添加“類名.”,該函數即為該類名對應類的拓展方法。

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

(2)成員方法優先

​ 如果被擴展的類的擴展方法與該類的成員方法名字和參數一樣,該類對象調用該方法時,調用的會是成員方法。

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

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

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

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

(4)擴展實現原理

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

​ kotlin擴展屬性、方法時看起來是為該類動態添加了成員,實際上並沒有真正修改這個被擴展的類,kotlin實質是定義了一個函數,當被擴展的類的對象調用擴展方法時,kotlin會執行靜態解析,將調用擴展函數靜態解析為函數調用。

靜態解析:根據調用對象、方法名找到拓展函數,轉換為函數調用。

如(2)str.lastIndex()方法執行的過程為:
​ ①檢查str類型(發現為String類型);

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

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

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

(5)靜態解析調用擴展方法注意點

​ 由於靜態調用擴展方法是在編譯時執行,因此,如果父類和子類都擴展了同名的一個擴展方法,引用類型均為父類的情況下,會調用父類的擴展方法。

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

/**
 * 父類
 */
open class ExtensionTest

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

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

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

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

​ kotlin允許擴展可空類型擴展方法,這樣,null也能調用該方法。

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.擴展屬性

(1)概念

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

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

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

(2)定義擴展屬性

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)

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

4.以類成員方式定義擴展

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

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

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

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

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

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

5.帶接收者的匿名擴展函數

(1)概念

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

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

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

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

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

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

(3)帶接收者的匿名擴展函數與lambda表達式

​ 如果能根據上下文推斷出接收者類型,則可以使用lambda表達式

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

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

/**
 * 定義一個函數,形參為ExtensionTest.(String) -> String類型,相當於同時為ExtensionTest類擴展了一個匿名擴展函數
 */
fun test(fn: ExtensionTest.(String) -> String) {
    val extensionTest = ExtensionTest()
    println("調用匿名擴展函數:${extensionTest.fn("匿名擴展函數傳入形參")}")
}

6.擴展使用場景

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

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM