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)
}
