Kotlin基礎-擴展

一、定義

Kotlin 在不修改類 / 不繼承類的情況下,向一個類添加新函數或者新屬性,更符合開閉原則。
擴展是一種靜態行為,對被擴展的類代碼本身不會造成任何影響。

  • 擴展屬性:定義在類或者kotlin文件中,不允許定義在函數中;
  • 擴展函數:擴展函數可以在已有類中添加新的方法,不會對原類做修改,擴展函數定義形式:
fun receiverType.functionName(params){
    body
}
//receiverType:表示函數的接收者,也就是函數擴展的對象
//functionName:擴展函數的名稱
//params:擴展函數的參數,可以為NULL

擴展的本質:擴展函數是定義在類外部的靜態函數,函數的第一個參數是接收者類型的對象。這意味著調用擴展時不會創建適配對象或者任何運行時的額外消耗。

二、擴展函數

1、擴展函數是靜態解析的,并不是接收者類型的虛擬成員

在調用擴展函數時,具體被調用的的是哪一個函數,由調用函數的的對象表達式來決定的,而不是動態的類型決定的:

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
2、若擴展函數和成員函數一致,則使用該函數時,會優先使用成員函數。
3、擴展一個空對象

在擴展函數內, 可以通過 this 來判斷接收者是否為 NULL,這樣,即使接收者為 NULL,也可以調用擴展函數。

fun Any?.toString(): String {
    if (this == null) return "null"
    // 空檢測之后,“this”會自動轉換為非空類型,所以下面的 toString()
    // 解析為 Any 類的成員函數
    return toString()
}

三、擴展屬性

擴展屬性允許定義在類或者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

伴生對象內的成員相當于 Java 中的靜態成員,其生命周期伴隨類始終,在伴生對象內部可以定義變量和函數,這些變量和函數可以直接用類名引用。
對于伴生對象擴展函數,有兩種形式,一種是在類內擴展,一種是在類外擴展,這兩種形式擴展后的函數互不影響(甚至名稱都可以相同),即使名稱相同,它們也完全是兩個不同的函數,并且有以下特點:
(1)類內擴展的伴隨對象函數和類外擴展的伴隨對象可以同名,它們是兩個獨立的函數,互不影響;
(2)當類內擴展的伴隨對象函數和類外擴展的伴隨對象同名時,類內的其它函數優先引用類內擴展的伴隨對象函數,即對于類內其它成員函數來說,類內擴展屏蔽類外擴展;
(3)類內擴展的伴隨對象函數只能被類內的函數引用,不能被類外的函數和伴隨對象內的函數引用;
(4)類外擴展的伴隨對象函數可以被伴隨對象內的函數引用;

五、擴展的作用域

通常擴展函數或屬性定義在頂級包下;
要使用所定義包之外的一個擴展, 通過import導入擴展的函數名進行使用;

六、擴展聲明為成員

  • 在一個類內部你可以為另一個類聲明擴展。
  • 在這個擴展中,有個多個隱含的接受者,其中擴展方法定義所在類的實例稱為分發接受者,而擴展方法的目標類型的實例稱為擴展接受者。
  • 以成員的形式定義的擴展函數, 可以聲明為 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

七、內置擴展函數

擴展函數是 Kotlin 用于簡化一些代碼的書寫產生的,其中有 五個函數

  • let
  • with
  • run
  • apply
  • also

1、let 函數

  1. 在函數塊內可以通過 it 指代該對象。
  2. 返回值為函數塊的最后一行或指定return表達式。
//一般寫法
val result = "hello".let {
  println(it.length)
  1000
}

使用場景:
1、使用let函數處理需要針對一個可null的對象統一做判空處理;
2、又或者是需要去明確一個變量所處特定的作用域范圍內可以使用;

2、with 函數

  1. 前面的幾個函數使用方式略有不同,因為它不是以擴展的形式存在的。它是將某對象作為函數的參數,在函數塊內可以通過 this 指代該對象。
  2. 返回值為函數塊的最后一行或指定return表達式。
//一般寫法
var result = with(Person()) {
    println(this.name + this.age)
    1000
}

使用場景:
1、適用于調用同一個類的多個方法時,可以省去類名重復,直接調用類的方法即可,經常用于Android中RecyclerView中onBinderViewHolder中,數據model的屬性映射到UI上

3、run 函數

  1. 實際上可以說是let和with兩個函數的結合體,run函數只接收一個lambda函數為參數,以閉包形式返回。
  2. 返回值為最后一行的值或者指定的return的表達式。
var result = person.run {
    println("$name + $age")
    1000
}

使用場景:
1、適用于let,with函數任何場景。
因為run函數是let,with兩個函數結合體,準確來說它彌補了let函數在函數體內必須使用it參數替代對象,在run函數中可以像with函數一樣可以省略,直接訪問實例的公有屬性和方法,另一方面它彌補了with函數傳入對象判空問題,在run函數中可以像let函數一樣做判空處理

4、apply 函數

  1. 從結構上來看apply函數和run函數很像,唯一不同點就是它們各自返回的值不一樣
  2. 返回值是傳入對象的本身
val person = Person().apply {
    name = "liming"
    age = 50
}

使用場景:
整體作用功能和run函數很像,唯一不同點就是它返回的值是對象本身,而run函數是一個閉包形式返回,返回的是最后一行的值。正是基于這一點差異它的適用場景稍微與run函數有點不一樣。
apply一般用于一個對象實例初始化的時候,需要對對象中的屬性進行賦值。或者動態inflate出一個XML的View的時候需要給View綁定數據也會用到,這種情景非常常見。特別是在我們開發中會有一些數據model向View model轉化實例化的過程中需要用到

5、also 函數

  1. also函數的結構實際上和let很像唯一的區別就是返回值的不一樣,let是以閉包的形式返回,返回函數體內最后一行的值,如果最后一行為空就返回一個Unit類型的默認值。
  2. 返回值是傳入對象的本身。
val result = "hello".also {
    println(it.length)
}

使用場景:
適用于let函數的任何場景,also函數和let很像,只是唯一的不同點就是let函數最后的返回值是最后一行的返回值而also函數的返回值是返回當前的這個對象。一般可用于多個擴展函數鏈式調用。

區別

方法名 是否有it 是否有this return類型
let ? × 最后一句
also ? × this
apply × ? this
run × ? 最后一句
with × ? 最后一句
為什么run、with、apply用this,also 和 let 用it?
  • run、with、apply 函數中的參數 block 是 「T 的擴展函數」,所以采用 this 是擴展函數的接收者對象(receiver)。另外因為 block 沒有參數,所以不存在 it 的定義。
  • also 和 let 參數 block 是 「參數為 T 的函數」,所以采用 it 是唯一參數(argument)。另外因為 block 不是擴展函數,所以不存在 this 的定義。
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,250評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,923評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,041評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,475評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,253評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,801評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,882評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,023評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,530評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,494評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,639評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,177評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,890評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,289評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,552評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,242評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,626評論 2 370

推薦閱讀更多精彩內容