一、定義
Kotlin 在不修改類 / 不繼承類的情況下,向一個類添加新函數(shù)或者新屬性,更符合開閉原則。
擴展是一種靜態(tài)行為,對被擴展的類代碼本身不會造成任何影響。
- 擴展屬性:定義在類或者kotlin文件中,不允許定義在函數(shù)中;
- 擴展函數(shù):擴展函數(shù)可以在已有類中添加新的方法,不會對原類做修改,擴展函數(shù)定義形式:
fun receiverType.functionName(params){
body
}
//receiverType:表示函數(shù)的接收者,也就是函數(shù)擴展的對象
//functionName:擴展函數(shù)的名稱
//params:擴展函數(shù)的參數(shù),可以為NULL
擴展的本質(zhì):擴展函數(shù)是定義在類外部的靜態(tài)函數(shù),函數(shù)的第一個參數(shù)是接收者類型的對象。這意味著調(diào)用擴展時不會創(chuàng)建適配對象或者任何運行時的額外消耗。
二、擴展函數(shù)
1、擴展函數(shù)是靜態(tài)解析的,并不是接收者類型的虛擬成員
在調(diào)用擴展函數(shù)時,具體被調(diào)用的的是哪一個函數(shù),由調(diào)用函數(shù)的的對象表達式來決定的,而不是動態(tài)的類型決定的:
open class C
class D: C()
fun C.foo() = "c" // 擴展函數(shù) foo
fun D.foo() = "d" // 擴展函數(shù) foo
fun printFoo(c: C) {
println(c.foo()) // 類型是 C 類
}
fun main(arg:Array<String>){
printFoo(D())
}
//實際輸出c
2、若擴展函數(shù)和成員函數(shù)一致,則使用該函數(shù)時,會優(yōu)先使用成員函數(shù)。
3、擴展一個空對象
在擴展函數(shù)內(nèi), 可以通過 this 來判斷接收者是否為 NULL,這樣,即使接收者為 NULL,也可以調(diào)用擴展函數(shù)。
fun Any?.toString(): String {
if (this == null) return "null"
// 空檢測之后,“this”會自動轉(zhuǎn)換為非空類型,所以下面的 toString()
// 解析為 Any 類的成員函數(shù)
return toString()
}
三、擴展屬性
擴展屬性允許定義在類或者kotlin文件中,不允許定義在函數(shù)中。初始化屬性因為屬性沒有后端字段(backing field),所以不允許被初始化,只能由顯式提供的 getter/setter 定義。
val Foo.bar = 1 // 錯誤:擴展屬性不能有初始化器
擴展屬性只能被聲明為 val
四、伴生對象的擴展
如果一個類定義有一個伴生對象 ,你也可以為伴生對象定義擴展函數(shù)和屬性。
伴生對象通過"類名."形式調(diào)用伴生對象,伴生對象聲明的擴展函數(shù),通過用類名限定符來調(diào)用:
class MyClass {
companion object { } // 將被稱為 "Companion"
}
fun MyClass.Companion.foo() {
println("伴隨對象的擴展函數(shù)")
}
val MyClass.Companion.no: Int
get() = 10
伴生對象內(nèi)的成員相當(dāng)于 Java 中的靜態(tài)成員,其生命周期伴隨類始終,在伴生對象內(nèi)部可以定義變量和函數(shù),這些變量和函數(shù)可以直接用類名引用。
對于伴生對象擴展函數(shù),有兩種形式,一種是在類內(nèi)擴展,一種是在類外擴展,這兩種形式擴展后的函數(shù)互不影響(甚至名稱都可以相同),即使名稱相同,它們也完全是兩個不同的函數(shù),并且有以下特點:
(1)類內(nèi)擴展的伴隨對象函數(shù)和類外擴展的伴隨對象可以同名,它們是兩個獨立的函數(shù),互不影響;
(2)當(dāng)類內(nèi)擴展的伴隨對象函數(shù)和類外擴展的伴隨對象同名時,類內(nèi)的其它函數(shù)優(yōu)先引用類內(nèi)擴展的伴隨對象函數(shù),即對于類內(nèi)其它成員函數(shù)來說,類內(nèi)擴展屏蔽類外擴展;
(3)類內(nèi)擴展的伴隨對象函數(shù)只能被類內(nèi)的函數(shù)引用,不能被類外的函數(shù)和伴隨對象內(nèi)的函數(shù)引用;
(4)類外擴展的伴隨對象函數(shù)可以被伴隨對象內(nèi)的函數(shù)引用;
五、擴展的作用域
通常擴展函數(shù)或?qū)傩远x在頂級包下;
要使用所定義包之外的一個擴展, 通過import導(dǎo)入擴展的函數(shù)名進行使用;
六、擴展聲明為成員
- 在一個類內(nèi)部你可以為另一個類聲明擴展。
- 在這個擴展中,有個多個隱含的接受者,其中擴展方法定義所在類的實例稱為分發(fā)接受者,而擴展方法的目標(biāo)類型的實例稱為擴展接受者。
- 以成員的形式定義的擴展函數(shù), 可以聲明為 open , 而且可以在子類中覆蓋. 也就是說, 在這類擴展函數(shù)的派 發(fā)過程中, 針對分發(fā)接受者是虛擬的(virtual), 但針對擴展接受者仍然是靜態(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)用擴展函數(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" —— 擴展接收者靜態(tài)解析
}
//實例執(zhí)行輸出結(jié)果為:
//D.foo in C
//D.foo in C1
//D.foo in C
七、內(nèi)置擴展函數(shù)
擴展函數(shù)是 Kotlin 用于簡化一些代碼的書寫產(chǎn)生的,其中有 五個函數(shù)
- let
- with
- run
- apply
- also
1、let 函數(shù)
- 在函數(shù)塊內(nèi)可以通過 it 指代該對象。
- 返回值為函數(shù)塊的最后一行或指定return表達式。
//一般寫法
val result = "hello".let {
println(it.length)
1000
}
使用場景:
1、使用let函數(shù)處理需要針對一個可null的對象統(tǒng)一做判空處理;
2、又或者是需要去明確一個變量所處特定的作用域范圍內(nèi)可以使用;
2、with 函數(shù)
- 前面的幾個函數(shù)使用方式略有不同,因為它不是以擴展的形式存在的。它是將某對象作為函數(shù)的參數(shù),在函數(shù)塊內(nèi)可以通過 this 指代該對象。
- 返回值為函數(shù)塊的最后一行或指定return表達式。
//一般寫法
var result = with(Person()) {
println(this.name + this.age)
1000
}
使用場景:
1、適用于調(diào)用同一個類的多個方法時,可以省去類名重復(fù),直接調(diào)用類的方法即可,經(jīng)常用于Android中RecyclerView中onBinderViewHolder中,數(shù)據(jù)model的屬性映射到UI上
3、run 函數(shù)
- 實際上可以說是let和with兩個函數(shù)的結(jié)合體,run函數(shù)只接收一個lambda函數(shù)為參數(shù),以閉包形式返回。
- 返回值為最后一行的值或者指定的return的表達式。
var result = person.run {
println("$name + $age")
1000
}
使用場景:
1、適用于let,with函數(shù)任何場景。
因為run函數(shù)是let,with兩個函數(shù)結(jié)合體,準(zhǔn)確來說它彌補了let函數(shù)在函數(shù)體內(nèi)必須使用it參數(shù)替代對象,在run函數(shù)中可以像with函數(shù)一樣可以省略,直接訪問實例的公有屬性和方法,另一方面它彌補了with函數(shù)傳入對象判空問題,在run函數(shù)中可以像let函數(shù)一樣做判空處理
4、apply 函數(shù)
- 從結(jié)構(gòu)上來看apply函數(shù)和run函數(shù)很像,唯一不同點就是它們各自返回的值不一樣
- 返回值是傳入對象的本身
val person = Person().apply {
name = "liming"
age = 50
}
使用場景:
整體作用功能和run函數(shù)很像,唯一不同點就是它返回的值是對象本身,而run函數(shù)是一個閉包形式返回,返回的是最后一行的值。正是基于這一點差異它的適用場景稍微與run函數(shù)有點不一樣。
apply一般用于一個對象實例初始化的時候,需要對對象中的屬性進行賦值?;蛘邉討B(tài)inflate出一個XML的View的時候需要給View綁定數(shù)據(jù)也會用到,這種情景非常常見。特別是在我們開發(fā)中會有一些數(shù)據(jù)model向View model轉(zhuǎn)化實例化的過程中需要用到
5、also 函數(shù)
- also函數(shù)的結(jié)構(gòu)實際上和let很像唯一的區(qū)別就是返回值的不一樣,let是以閉包的形式返回,返回函數(shù)體內(nèi)最后一行的值,如果最后一行為空就返回一個Unit類型的默認(rèn)值。
- 返回值是傳入對象的本身。
val result = "hello".also {
println(it.length)
}
使用場景:
適用于let函數(shù)的任何場景,also函數(shù)和let很像,只是唯一的不同點就是let函數(shù)最后的返回值是最后一行的返回值而also函數(shù)的返回值是返回當(dāng)前的這個對象。一般可用于多個擴展函數(shù)鏈?zhǔn)秸{(diào)用。
區(qū)別
| 方法名 | 是否有it | 是否有this | return類型 |
|---|---|---|---|
| let | ? | × | 最后一句 |
| also | ? | × | this |
| apply | × | ? | this |
| run | × | ? | 最后一句 |
| with | × | ? | 最后一句 |
為什么run、with、apply用this,also 和 let 用it?
- run、with、apply 函數(shù)中的參數(shù) block 是 「T 的擴展函數(shù)」,所以采用 this 是擴展函數(shù)的接收者對象(receiver)。另外因為 block 沒有參數(shù),所以不存在 it 的定義。
- also 和 let 參數(shù) block 是 「參數(shù)為 T 的函數(shù)」,所以采用 it 是唯一參數(shù)(argument)。另外因為 block 不是擴展函數(shù),所以不存在 this 的定義。