擴(kuò)展是Kotlin中特別強(qiáng)大的一個(gè)功能,如擴(kuò)展函數(shù)仰美,本文是學(xué)習(xí)Kotlin中的擴(kuò)展(Extensions)和This表達(dá)式的相關(guān)知識(shí)迷殿。
擴(kuò)展(Extensions)
在Java開(kāi)發(fā)時(shí),會(huì)經(jīng)常將那些共用的方法寫到一個(gè)Utils
類咖杂,如FileUtils
,StringUtils
等等庆寺。很有名的java.util.Collections
也是其中一員的,在使用的時(shí)候
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list))
Kotlin中提供了一種可以在不繼承父類诉字,也不使用類似Decorator
這樣的設(shè)計(jì)模式的情況下對(duì)指定類進(jìn)行擴(kuò)展懦尝,在Kotlin中稱為擴(kuò)展的特殊聲明,支持函數(shù)擴(kuò)展和屬性擴(kuò)展壤圃。
如上面的可以寫成
list.swap(list.binarySearch(otherList.max()), list.max())
比如要將Toast
寫成可以直接調(diào)用toast(this, "toast")
fun Context.toast(context: Context, content: String) {
Toast.makeText(context, content, Toast.LENGTH_SHORT).show()
}
擴(kuò)展函數(shù)(Extension Functions)
要聲明一個(gè)擴(kuò)展函數(shù)陵霉,我們需要在函數(shù)的名稱前加上一個(gè)接收者類型并且加上.
符號(hào)
/**
* MutableList<Int> 添加一個(gè) swap() 擴(kuò)展函數(shù)
*/
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // this對(duì)應(yīng)list
this[index1] = this[index2]
this[index2] = tmp
}
在擴(kuò)展函數(shù)中的this
關(guān)鍵字表示接收者對(duì)象,下面會(huì)講到this
表達(dá)式伍绳,在這里的this
對(duì)應(yīng)MutableList<Int>
類型的對(duì)象踊挠。
val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 在 `swap()` 函數(shù)中 `this` 持有的值是 `l`
如果要改成對(duì)任意類型MutableList<T>
都適用
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1]
this[index1] = this[index2]
this[index2] = tmp
}
像在集合Collections
里面就可以找到很多的擴(kuò)展函數(shù):
擴(kuò)展是靜態(tài)解析(Extensions are resolved statically)
由于Kotlin的擴(kuò)展是靜態(tài)解析的,所以在定義了一個(gè)擴(kuò)展函數(shù)之后冲杀,并不會(huì)給這個(gè)類添加方法止毕,而是讓這個(gè)方法能夠被這個(gè)類的實(shí)例對(duì)象通過(guò).
來(lái)調(diào)用。舉例
open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
在調(diào)用printFoo(D())
的話會(huì)打印c
還是d
漠趁,答案是打印c
扁凛,因?yàn)樵诙x擴(kuò)展函數(shù)的時(shí)候接收對(duì)象類型是C
類,而且Kotlin的擴(kuò)展是靜態(tài)解析的闯传,所以即使調(diào)用的時(shí)候是傳了C
類的子類D
類進(jìn)去谨朝,還是會(huì)執(zhí)行定義的時(shí)候的類型的函數(shù)。
那如果類中有個(gè)函數(shù)foo()
甥绿,然后在寫個(gè)擴(kuò)展函數(shù)C.foo()
字币,這時(shí)候如果調(diào)用的話,是調(diào)用哪個(gè)函數(shù)呢共缕?
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }
答案是調(diào)用類的成員函數(shù)洗出,如果有同名同參數(shù)的成員函數(shù)和擴(kuò)展函數(shù),調(diào)用的時(shí)候必然會(huì)使用成員函數(shù)图谷,所以這里會(huì)打印member
擴(kuò)展函數(shù)完全可以重載具有相同名稱但不同參數(shù)的成員函數(shù)
class C {
fun foo() { println("number") }
}
fun C.foo(i:Int) { println("extention") }
總結(jié)一下翩活,如果擴(kuò)展函數(shù)和成員函數(shù)同名同參數(shù)阱洪,則成員函數(shù)優(yōu)先級(jí)高于擴(kuò)展函數(shù)。
可空接收者(Nullable Receiver)
Kotlin的擴(kuò)展支持接收者對(duì)象為空菠镇,舉個(gè)例子冗荸,在調(diào)用字符串的toString()
函數(shù)的時(shí)候,如果字符串為null
的話會(huì)報(bào)空指針異常利耍,這里可以改成擴(kuò)展函數(shù)蚌本,里面將傳進(jìn)來(lái)的字符串進(jìn)行this == null
判斷,就可以在 Kotlin 中任意調(diào)用toString()
函數(shù)而不進(jìn)行空指針檢查隘梨。
fun Any?.toString(): String {
if (this == null) return "null"
// 在空檢查之后程癌,`this` 被自動(dòng)轉(zhuǎn)為非空類型,因此 toString() 可以被解析到任何類的成員函數(shù)中
return toString()
}
屬性擴(kuò)展(Extension Properties)
與函數(shù)類似轴猎,Kotlin支持?jǐn)U展屬性
val <T> List<T>.lastIndex: Int
get() = size - 1
前面說(shuō)過(guò)嵌莉,Kotlin是靜態(tài)解析擴(kuò)展的,所以擴(kuò)展屬性不會(huì)有備用字段税稼,這也是為什么初始化函數(shù)不允許有擴(kuò)展屬性,擴(kuò)展屬性只能夠通過(guò)明確提供getter
和setter
函數(shù)來(lái)進(jìn)行定義垮斯。
val Foo.bar = 1 // 錯(cuò)誤: 初始化函數(shù)不允許有擴(kuò)展屬性
伴生對(duì)象擴(kuò)展(Companion Object Extensions)
如果一個(gè)類有伴生對(duì)象郎仆,同樣可以為伴隨對(duì)象定義擴(kuò)展函數(shù)和屬性
class MyClass {
companion object { }
}
fun MyClass.Companion.foo() {
// ...
}
調(diào)用的時(shí)候
MyClass.foo()
擴(kuò)展的范圍
在前面說(shuō)過(guò)Top-level
是與類同級(jí)的,大多數(shù)情況下兜蠕,擴(kuò)展也可以在Top-level
層級(jí)定義扰肌。
package foo.bar
fun Baz.goo() { ... }
在使用的時(shí)候與一般類使用一樣,要import
導(dǎo)入包熊杨。
package com.example,usage
import foo.bar.goo // 導(dǎo)入所有名字叫 "goo" 的擴(kuò)展
// 或者
import foo.bar.* // 導(dǎo)入foo.bar包下得所有數(shù)據(jù)
fun usage(baz: Baz) {
baz.goo()
}
This表達(dá)式(This Expression)
上面說(shuō)了曙旭,在擴(kuò)展函數(shù)中的this
關(guān)鍵字對(duì)應(yīng)接收者對(duì)象,this
表示當(dāng)前接收者晶府,用到了this
表達(dá)式桂躏。
- 在類的成員中,
this
表示當(dāng)前類的對(duì)象 - 在擴(kuò)展函數(shù)或帶接收者的文本函數(shù)中川陆,
this
在.
的左邊剂习,表示接收者參數(shù)
This表達(dá)式的限制(Qualified this)
為了在范圍外部(類、擴(kuò)展函數(shù)较沪、帶接收者的文本函數(shù)訪問(wèn)this
鳞绕,需要使用到@label
,在前面也說(shuō)過(guò)return
到標(biāo)簽(@label
)尸曼,這里用this@label
表示
class A { // 默認(rèn)有@A標(biāo)簽
// this表示A
val aThis = this
inner class B { // 默認(rèn)有@B標(biāo)簽
// this表示B
val bThis = this
fun Int.foo() { // 默認(rèn)有@foo標(biāo)簽
// 表示A
val a = this@A
// 表示B
val b = this@B
// this表示帶接收者為Int的文本函數(shù)foo()
val c = this
// 表示帶接收者為Int的文本函數(shù)foo()
val c1 = this@foo
}
}
}
將下面的代碼運(yùn)行们何,會(huì)打印出什么呢?
運(yùn)行的結(jié)果為: