Kotlin 擴展

2022-02-18 10:59 更新

Kotlin 可以對一個類的屬性和方法進行擴展,且不需要繼承或使用 Decorator 模式。

擴展是一種靜態(tài)行為,對被擴展的類代碼本身不會造成任何影響。

擴展函數(shù)

擴展函數(shù)可以在已有類中添加新的方法,不會對原類做修改,擴展函數(shù)定義形式:

fun receiverType.functionName(params){
    body
}
  • receiverType:表示函數(shù)的接收者,也就是函數(shù)擴展的對象
  • functionName:擴展函數(shù)的名稱
  • params:擴展函數(shù)的參數(shù),可以為NULL

以下實例擴展 User 類 :

class User(var name:String)

/**擴展函數(shù)**/
fun User.Print(){
    print("用戶名 $name")
}

fun main(arg:Array<String>){
    var user = User("W3cschool")
    user.Print()
}

實例執(zhí)行輸出結果為:

用戶名 W3cschool

下面代碼為 MutableList 添加一個swap 函數(shù):

// 擴展函數(shù) 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()' 函數(shù)內的 'this' 將指向 'l' 的值

    println(l.toString())
}

實例執(zhí)行輸出結果為:

[3, 2, 1]

this關鍵字指代接收者對象(receiver object)(也就是調用擴展函數(shù)時, 在點號之前指定的對象實例)。

擴展函數(shù)是靜態(tài)解析的

擴展函數(shù)是靜態(tài)解析的,并不是接收者類型的虛擬成員,在調用擴展函數(shù)時,具體被調用的的是哪一個函數(shù),由調用函數(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())
}

實例執(zhí)行輸出結果為:

c

若擴展函數(shù)和成員函數(shù)一致,則使用該函數(shù)時,會優(yōu)先使用成員函數(shù)。

class C {
    fun foo() { println("成員函數(shù)") }
}

fun C.foo() { println("擴展函數(shù)") }

fun main(arg:Array<String>){
    var c = C()
    c.foo()
}

實例執(zhí)行輸出結果為:

成員函數(shù)

擴展一個空對象

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

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

實例執(zhí)行輸出結果為:

null

擴展屬性

除了函數(shù),Kotlin 也支持屬性對屬性進行擴展:

val <T> List<T>.lastIndex: Int
    get() = size - 1
 

擴展屬性允許定義在類或者kotlin文件中,不允許定義在函數(shù)中。初始化屬性因為屬性沒有后端字段(backing field),所以不允許被初始化,只能由顯式提供的 getter/setter 定義。

val Foo.bar = 1 // 錯誤:擴展屬性不能有初始化器

擴展屬性只能被聲明為 val。

伴生對象的擴展

如果一個類定義有一個伴生對象 ,你也可以為伴生對象定義擴展函數(shù)和屬性。

伴生對象通過"類名."形式調用伴生對象,伴生對象聲明的擴展函數(shù),通過用類名限定符來調用:

class MyClass {
    companion object { }  // 將被稱為 "Companion"
}

fun MyClass.Companion.foo() {
    println("伴隨對象的擴展函數(shù)")
}

val MyClass.Companion.no: Int
    get() = 10

fun main(args: Array<String>) {
    println("no:${MyClass.no}")
    MyClass.foo()
}

實例執(zhí)行輸出結果為:

no:10
伴隨對象的擴展函數(shù)

擴展的作用域

通常擴展函數(shù)或屬性定義在頂級包下:

package foo.bar

fun Baz.goo() { …… } 

要使用所定義包之外的一個擴展, 通過import導入擴展的函數(shù)名進行使用:

package com.example.usage

import foo.bar.goo // 導入所有名為 goo 的擴展
                   // 或者
import foo.bar.*   // 從 foo.bar 導入一切

fun usage(baz: Baz) {
    baz.goo()
}

擴展聲明為成員

在一個類內部你可以為另一個類聲明擴展。

在這個擴展中,有個多個隱含的接受者,其中擴展方法定義所在類的實例稱為分發(fā)接受者,而擴展方法的目標類型的實例稱為擴展接受者。

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()   // 調用擴展函數(shù)
    }
}

fun main(args: Array<String>) {
    val c: C = C()
    val d: D = D()
    c.caller(d)

}

實例執(zhí)行輸出結果為:

D bar
C baz

在 C 類內,創(chuàng)建了 D 類的擴展。此時,C 被成為分發(fā)接受者,而 D 為擴展接受者。從上例中,可以清楚的看到,在擴展函數(shù)中,可以調用派發(fā)接收者的成員函數(shù)。

假如在調用某一個函數(shù),而該函數(shù)在分發(fā)接受者和擴展接受者均存在,則以擴展接收者優(yōu)先,要引用分發(fā)接收者的成員你可以使用限定的 this 語法。

class D {
    fun bar() { println("D bar") }
}

class C {
    fun bar() { println("C bar") }  // 與 D 類 的 bar 同名

    fun D.foo() {
        bar()         // 調用 D.bar(),擴展接收者優(yōu)先
        this@C.bar()  // 調用 C.bar()
    }

    fun caller(d: D) {
        d.foo()   // 調用擴展函數(shù)
    }
}

fun main(args: Array<String>) {
    val c: C = C()
    val d: D = D()
    c.caller(d)

}

實例執(zhí)行輸出結果為:

D bar
C bar

以成員的形式定義的擴展函數(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()   // 調用擴展函數(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í)行輸出結果為:

D.foo in C
D.foo in C1
D.foo in C


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號