Kotlin 擴充套件
Kotlin 可以對一個類的屬性和方法進行擴充套件,且不需要繼承或使用 Decorator 模式。
擴充套件是一種靜態行為,對被擴充套件的類程式碼本身不會造成任何影響。
擴充套件函式
擴充套件函式可以在已有類中新增新的方法,不會對原類做修改,擴充套件函式定義形式:
fun receiverType.functionName(params){ body }
- receiverType:表示函式的接收者,也就是函式擴充套件的物件
- functionName:擴充套件函式的名稱
- params:擴充套件函式的引數,可以為NULL
以下例項擴充套件 User 類 :
class User(var name:String) /**擴充套件函式**/ fun User.Print(){ print("使用者名稱 $name") } fun main(arg:Array<String>){ var user = User("itread01") user.Print() }
例項執行輸出結果為:
使用者名稱 itread01
下面程式碼為 MutableList
// 擴充套件函式 swap,調換不同位置的值 fun MutableList<Int>.swap(index1: Int, index2: Int) { val tmp = this[index1] // this 對應該列表 this[index1] = this[index2] this[index2] = tmp } fun main(args: Array<String>) { val l = mutableListOf(1, 2, 3) // 位置 0 和 2 的值做了互換 l.swap(0, 2) // 'swap()' 函式內的 'this' 將指向 'l' 的值 println(l.toString()) }
例項執行輸出結果為:
[3, 2, 1]
this關鍵字指代接收者物件(receiver object)(也就是呼叫擴充套件函式時, 在點號之前指定的物件例項)。
擴充套件函式是靜態解析的
擴充套件函式是靜態解析的,並不是接收者型別的虛擬成員,在呼叫擴充套件函式時,具體被呼叫的的是哪一個函式,由呼叫函式的的物件表示式來決定的,而不是動態的型別決定的:
open class C class D: C() fun C.foo() = "c" // 擴充套件函式 foo fun D.foo() = "d" // 擴充套件函式 foo fun printFoo(c: C) { println(c.foo()) // 型別是 C 類 } fun main(arg:Array<String>){ printFoo(D()) }
例項執行輸出結果為:
c
若擴充套件函式和成員函式一致,則使用該函式時,會優先使用成員函式。
class C { fun foo() { println("成員函式") } } fun C.foo() { println("擴充套件函式") } fun main(arg:Array<String>){ var c = C() c.foo() }
例項執行輸出結果為:
成員函式
擴充套件一個空物件
在擴充套件函式內, 可以通過 this 來判斷接收者是否為 NULL,這樣,即使接收者為 NULL,也可以呼叫擴充套件函式。例如:
fun Any?.toString(): String { if (this == null) return "null" // 空檢測之後,“this”會自動轉換為非空型別,所以下面的 toString() // 解析為 Any 類的成員函式 return toString() } fun main(arg:Array<String>){ var t = null println(t.toString()) }
例項執行輸出結果為:
null
擴充套件屬性
除了函式,Kotlin 也支援屬性對屬性進行擴充套件:
val <T> List<T>.lastIndex: Int get() = size - 1
擴充套件屬性允許定義在類或者kotlin檔案中,不允許定義在函式中。初始化屬性因為屬性沒有後端欄位(backing field),所以不允許被初始化,只能由顯式提供的 getter/setter 定義。
val Foo.bar = 1 // 錯誤:擴充套件屬性不能有初始化器
擴充套件屬性只能被宣告為 val。
伴生物件的擴充套件
如果一個類定義有一個伴生物件 ,你也可以為伴生物件定義擴充套件函式和屬性。
伴生物件通過"類名."形式呼叫伴生物件,伴生物件宣告的擴充套件函式,通過用類名限定符來呼叫:
class MyClass { companion object { } // 將被稱為 "Companion" } fun MyClass.Companion.foo() { println("伴隨物件的擴充套件函式") } val MyClass.Companion.no: Int get() = 10 fun main(args: Array<String>) { println("no:${MyClass.no}") MyClass.foo() }
例項執行輸出結果為:
no:10 伴隨物件的擴充套件函式
擴充套件的作用域
通常擴充套件函式或屬性定義在頂級包下:
package foo.bar fun Baz.goo() { …… }
要使用所定義包之外的一個擴充套件, 通過import匯入擴充套件的函式名進行使用:
package com.example.usage import foo.bar.goo // 匯入所有名為 goo 的擴充套件 // 或者 import foo.bar.* // 從 foo.bar 匯入一切 fun usage(baz: Baz) { baz.goo() }
擴充套件宣告為成員
在一個類內部你可以為另一個類宣告擴充套件。
在這個擴充套件中,有個多個隱含的接受者,其中擴充套件方法定義所在類的例項稱為分發接受者,而擴充套件方法的目標型別的例項稱為擴充套件接受者。
class D { fun bar() { println("D bar") } } class C { fun baz() { println("C baz") } fun D.foo() { bar() // 呼叫 D.bar baz() // 呼叫 C.baz } fun caller(d: D) { d.foo() // 呼叫擴充套件函式 } } fun main(args: Array<String>) { val c: C = C() val d: D = D() c.caller(d) }
例項執行輸出結果為:
D bar C baz
在 C 類內,建立了 D 類的擴充套件。此時,C 被成為分發接受者,而 D 為擴充套件接受者。從上例中,可以清楚的看到,在擴充套件函式中,可以呼叫派發接收者的成員函式。
假如在呼叫某一個函式,而該函式在分發接受者和擴充套件接受者均存在,則以擴充套件接收者優先,要引用分發接收者的成員你可以使用限定的 this 語法。
class D { fun bar() { println("D bar") } } class C { fun bar() { println("C bar") } // 與 D 類 的 bar 同名 fun D.foo() { bar() // 呼叫 D.bar(),擴充套件接收者優先 [email protected]() // 呼叫 C.bar() } fun caller(d: D) { d.foo() // 呼叫擴充套件函式 } } fun main(args: Array<String>) { val c: C = C() val d: D = D() c.caller(d) }
例項執行輸出結果為:
D bar C bar
以成員的形式定義的擴充套件函式, 可以宣告為 open , 而且可以在子類中覆蓋. 也就是說, 在這類擴充套件函式的派 發過程中, 針對分發接受者是虛擬的(virtual), 但針對擴充套件接受者仍然是靜態的。
open class D { } class D1 : D() { } open class C { open fun D.foo() { println("D.foo in C") } open fun D1.foo() { println("D1.foo in C") } fun caller(d: D) { d.foo() // 呼叫擴充套件函式 } } class C1 : C() { override fun D.foo() { println("D.foo in C1") } override fun D1.foo() { println("D1.foo in C1") } } fun main(args: Array<String>) { C().caller(D()) // 輸出 "D.foo in C" C1().caller(D()) // 輸出 "D.foo in C1" —— 分發接收者虛擬解析 C().caller(D1()) // 輸出 "D.foo in C" —— 擴充套件接收者靜態解析 }
例項執行輸出結果為:
D.foo in C D.foo in C1 D.foo in C