Kotlin 可以對(duì)一個(gè)類(lèi)的屬性和方法進(jìn)行擴(kuò)展,且不需要繼承或使用 Decorator 模式。
擴(kuò)展是一種靜態(tài)行為,對(duì)被擴(kuò)展的類(lèi)代碼本身不會(huì)造成任何影響。
擴(kuò)展函數(shù)可以在已有類(lèi)中添加新的方法,不會(huì)對(duì)原類(lèi)做修改,擴(kuò)展函數(shù)定義形式:
fun receiverType.functionName(params){
body
}
以下實(shí)例擴(kuò)展 User 類(lèi) :
class User(var name:String)
/**擴(kuò)展函數(shù)**/
fun User.Print(){
print("用戶(hù)名 $name")
}
fun main(arg:Array<String>){
var user = User("W3cschool")
user.Print()
}
實(shí)例執(zhí)行輸出結(jié)果為:
用戶(hù)名 W3cschool
下面代碼為 MutableList 添加一個(gè)swap 函數(shù):
// 擴(kuò)展函數(shù) swap,調(diào)換不同位置的值
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // this 對(duì)應(yīng)該列表
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()' 函數(shù)內(nèi)的 'this' 將指向 'l' 的值
println(l.toString())
}
實(shí)例執(zhí)行輸出結(jié)果為:
[3, 2, 1]
this關(guān)鍵字指代接收者對(duì)象(receiver object)(也就是調(diào)用擴(kuò)展函數(shù)時(shí), 在點(diǎn)號(hào)之前指定的對(duì)象實(shí)例)。
擴(kuò)展函數(shù)是靜態(tài)解析的,并不是接收者類(lèi)型的虛擬成員,在調(diào)用擴(kuò)展函數(shù)時(shí),具體被調(diào)用的的是哪一個(gè)函數(shù),由調(diào)用函數(shù)的的對(duì)象表達(dá)式來(lái)決定的,而不是動(dòng)態(tài)的類(lèi)型決定的:
open class C
class D: C()
fun C.foo() = "c" // 擴(kuò)展函數(shù) foo
fun D.foo() = "d" // 擴(kuò)展函數(shù) foo
fun printFoo(c: C) {
println(c.foo()) // 類(lèi)型是 C 類(lèi)
}
fun main(arg:Array<String>){
printFoo(D())
}
實(shí)例執(zhí)行輸出結(jié)果為:
c
若擴(kuò)展函數(shù)和成員函數(shù)一致,則使用該函數(shù)時(shí),會(huì)優(yōu)先使用成員函數(shù)。
class C {
fun foo() { println("成員函數(shù)") }
}
fun C.foo() { println("擴(kuò)展函數(shù)") }
fun main(arg:Array<String>){
var c = C()
c.foo()
}
實(shí)例執(zhí)行輸出結(jié)果為:
成員函數(shù)
在擴(kuò)展函數(shù)內(nèi), 可以通過(guò) this 來(lái)判斷接收者是否為 NULL,這樣,即使接收者為 NULL,也可以調(diào)用擴(kuò)展函數(shù)。例如:
fun Any?.toString(): String {
if (this == null) return "null"
// 空檢測(cè)之后,“this”會(huì)自動(dòng)轉(zhuǎn)換為非空類(lèi)型,所以下面的 toString()
// 解析為 Any 類(lèi)的成員函數(shù)
return toString()
}
fun main(arg:Array<String>){
var t = null
println(t.toString())
}
實(shí)例執(zhí)行輸出結(jié)果為:
null
擴(kuò)展屬性
除了函數(shù),Kotlin 也支持屬性對(duì)屬性進(jìn)行擴(kuò)展:
val <T> List<T>.lastIndex: Int
get() = size - 1
擴(kuò)展屬性允許定義在類(lèi)或者kotlin文件中,不允許定義在函數(shù)中。初始化屬性因?yàn)閷傩詻](méi)有后端字段(backing field),所以不允許被初始化,只能由顯式提供的 getter/setter 定義。
val Foo.bar = 1 // 錯(cuò)誤:擴(kuò)展屬性不能有初始化器
擴(kuò)展屬性只能被聲明為 val。
如果一個(gè)類(lèi)定義有一個(gè)伴生對(duì)象 ,你也可以為伴生對(duì)象定義擴(kuò)展函數(shù)和屬性。
伴生對(duì)象通過(guò)"類(lèi)名."形式調(diào)用伴生對(duì)象,伴生對(duì)象聲明的擴(kuò)展函數(shù),通過(guò)用類(lèi)名限定符來(lái)調(diào)用:
class MyClass {
companion object { } // 將被稱(chēng)為 "Companion"
}
fun MyClass.Companion.foo() {
println("伴隨對(duì)象的擴(kuò)展函數(shù)")
}
val MyClass.Companion.no: Int
get() = 10
fun main(args: Array<String>) {
println("no:${MyClass.no}")
MyClass.foo()
}
實(shí)例執(zhí)行輸出結(jié)果為:
no:10
伴隨對(duì)象的擴(kuò)展函數(shù)
通常擴(kuò)展函數(shù)或?qū)傩远x在頂級(jí)包下:
package foo.bar
fun Baz.goo() { …… }
要使用所定義包之外的一個(gè)擴(kuò)展, 通過(guò)import導(dǎo)入擴(kuò)展的函數(shù)名進(jìn)行使用:
package com.example.usage
import foo.bar.goo // 導(dǎo)入所有名為 goo 的擴(kuò)展
// 或者
import foo.bar.* // 從 foo.bar 導(dǎo)入一切
fun usage(baz: Baz) {
baz.goo()
}
在一個(gè)類(lèi)內(nèi)部你可以為另一個(gè)類(lèi)聲明擴(kuò)展。
在這個(gè)擴(kuò)展中,有個(gè)多個(gè)隱含的接受者,其中擴(kuò)展方法定義所在類(lèi)的實(shí)例稱(chēng)為分發(fā)接受者,而擴(kuò)展方法的目標(biāo)類(lèi)型的實(shí)例稱(chēng)為擴(kuò)展接受者。
class D {
fun bar() { println("D bar") }
}
class C {
fun baz() { println("C baz") }
fun D.foo() {
bar() // 調(diào)用 D.bar
baz() // 調(diào)用 C.baz
}
fun caller(d: D) {
d.foo() // 調(diào)用擴(kuò)展函數(shù)
}
}
fun main(args: Array<String>) {
val c: C = C()
val d: D = D()
c.caller(d)
}
實(shí)例執(zhí)行輸出結(jié)果為:
D bar
C baz
在 C 類(lèi)內(nèi),創(chuàng)建了 D 類(lèi)的擴(kuò)展。此時(shí),C 被成為分發(fā)接受者,而 D 為擴(kuò)展接受者。從上例中,可以清楚的看到,在擴(kuò)展函數(shù)中,可以調(diào)用派發(fā)接收者的成員函數(shù)。
假如在調(diào)用某一個(gè)函數(shù),而該函數(shù)在分發(fā)接受者和擴(kuò)展接受者均存在,則以擴(kuò)展接收者優(yōu)先,要引用分發(fā)接收者的成員你可以使用限定的 this 語(yǔ)法。
class D {
fun bar() { println("D bar") }
}
class C {
fun bar() { println("C bar") } // 與 D 類(lèi) 的 bar 同名
fun D.foo() {
bar() // 調(diào)用 D.bar(),擴(kuò)展接收者優(yōu)先
this@C.bar() // 調(diào)用 C.bar()
}
fun caller(d: D) {
d.foo() // 調(diào)用擴(kuò)展函數(shù)
}
}
fun main(args: Array<String>) {
val c: C = C()
val d: D = D()
c.caller(d)
}
實(shí)例執(zhí)行輸出結(jié)果為:
D bar
C bar
以成員的形式定義的擴(kuò)展函數(shù), 可以聲明為 open , 而且可以在子類(lèi)中覆蓋. 也就是說(shuō), 在這類(lèi)擴(kuò)展函數(shù)的派 發(fā)過(guò)程中, 針對(duì)分發(fā)接受者是虛擬的(virtual), 但針對(duì)擴(kuò)展接受者仍然是靜態(tài)的。
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() // 調(diào)用擴(kuò)展函數(shù)
}
}
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" —— 分發(fā)接收者虛擬解析
C().caller(D1()) // 輸出 "D.foo in C" —— 擴(kuò)展接收者靜態(tài)解析
}
實(shí)例執(zhí)行輸出結(jié)果為:
D.foo in C
D.foo in C1
D.foo in C
更多建議: