Kotlin擴展

Kotlin與C#和Go類似,提供了擴展一個新功能的類,而不必繼承類或使用任何類型的設計模式,如Decorator。 這是通過稱爲擴展名的特殊聲明完成的。 Kotlin支持擴展功能和擴展屬性。

擴展函數

要聲明一個擴展函數,需要用一個接收器類型,即被擴展的類型來加上它的名字。 以下爲MutableList <Int>添加swap函數:

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' corresponds to the list
    this[index1] = this[index2]
    this[index2] = tmp
}

擴展函數中的this關鍵字對應於接收器對象(在點之前傳遞的對象)。 現在,可以在任何MutableList <Int>上調用這樣一個函數:

val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'l'

當然,這個函數對於任何MutableList <T>是有意義的,可以將它通用化:

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' corresponds to the list
    this[index1] = this[index2]
    this[index2] = tmp
}

在函數名稱之前聲明通用類型參數,使其在接收器類型表達式中可用。 請參閱通用功能。

擴展程序被靜態解析

擴展不會實際修改它們擴展的類。 通過定義擴展名,不將新成員插入到類中,而只能使用這種類型的變量上的點符號來調用新的函數。

擴展功能是靜態調度的,即它們不是接收器類型的虛擬機。 這意味着被調用的擴展函數由調用該函數的表達式的類型決定,而不是在運行時評估該表達式的結果的類型。 例如:

open class C

class D: C()

fun C.foo() = "c"

fun D.foo() = "d"

fun printFoo(c: C) {
    println(c.foo())
}

printFoo(D())

此示例將打印「c」,因爲被調用的擴展函數僅取決於參數c(C類)的聲明類型。

該示例將打印「c」,因爲類的calleIf的擴展函數具有成員函數,並且定義了擴展函數,其具有相同的接收器類型,相同的名稱並且適用於給定的參數(該成員始終優先)。 例如:

class C {
    fun foo() { println("member") }
}

fun C.foo() { println("extension") }

如果調用c類型的c.foo(),它將打印「member」而不是「extension」。

但是,擴展函數可以重載具有相同名稱但不同簽名的成員函數,這是完全可行的:

class C {
    fun foo() { println("member") }
}

fun C.foo(i: Int) { println("extension") }

C().foo(1)的調用將打印「extension」。

可接受Null的接收器

請注意,可以使用可空(null)接收器類型定義擴展。 這樣的擴展可以在一個對象變量上調用,即使它的值爲null,並且可以在主體內檢查this == null。 這樣就可以在Kotlin中調用toString(),而無需檢查null:檢查發生在擴展函數內。

fun Any?.toString(): String {
    if (this == null) return "null"
    // after the null check, 'this' is autocast to a non-null type, so the toString() below
    // resolves to the member function of the Any class
    return toString()
}

擴展屬性

與函數類似,Kotlin支持擴展屬性:

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

請注意,由於擴展名實際上並不將成員插入到類中,因此擴展屬性沒有有效的方式來添加後備字段。 這就是爲什麼不允許擴展屬性的初始化器。 它們的行爲只能通過明確提供getter / setter來定義。

val Foo.bar = 1 // error: initializers are not allowed for extension properties

伴隨對象擴展

如果一個類定義了一個伴隨對象,那麼還可以定義該對象的擴展函數和屬性:

class MyClass {
    companion object { }  // will be called "Companion"
}

fun MyClass.Companion.foo() {
    // ...
}

就像伴隨對象的常規成員一樣,只能使用類名作爲限定詞:

MyClass.foo()

擴展範圍

大多數時候在頂層定義擴展,即直接在包下:

package foo.bar

fun Baz.goo() { ... }

要在其聲明包之外使用這樣的擴展,需要在調用時導入它:

package com.yiibai.usage

import foo.bar.goo // importing all extensions by name "goo"
                   // or
import foo.bar.*   // importing everything from "foo.bar"

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

有關詳細信息,請參閱導入。

聲明擴展作爲成員

在類中,可以爲另一個類聲明擴展名。 在這樣的擴展中,有多個隱式接收器 - 可以在沒有限定符的情況下訪問對象成員。 聲明擴展名的類的實例稱爲調度接收方,擴展方法的接收方型稱爲擴展接收方。

class D {
    fun bar() { ... }
}

class C {
    fun baz() { ... }

    fun D.foo() {
        bar()   // calls D.bar
        baz()   // calls C.baz
    }

    fun caller(d: D) {
        d.foo()   // call the extension function
    }
}

在發送接收器的成員與分發接收器之間發生名稱衝突的情況下,分發接收器優先。 要引用發送接收器的成員,可以使用合格的this語法。

class C {
    fun D.foo() {
        toString()         // calls D.toString()
        [email protected]()  // calls C.toString()
    }

聲明爲成員的擴展可以被聲明爲在子類中打開(open)和覆蓋。 這意味着這種函數調度對於調度接收器類型是虛擬的,但是關於擴展接收器類型是靜態的。

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()   // call the extension function
    }
}

class C1 : C() {
    override fun D.foo() {
        println("D.foo in C1")
    }

    override fun D1.foo() {
        println("D1.foo in C1")
    }
}

C().caller(D())   // prints "D.foo in C"
C1().caller(D())  // prints "D.foo in C1" - dispatch receiver is resolved virtually
C().caller(D1())  // prints "D.foo in C" - extension receiver is resolved statically

動機

在Java中,我們習慣使用名爲「*Utils」的類:FileUtilsStringUtils等。java.util.Collections也屬於這一類用法。 關於這些Utils類的令人不快的部分,如下代碼所示:

// Java
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list))

這些類名總是要明確寫出來,但是可以使用靜態導入並得到:

// Java
swap(list, binarySearch(list, max(otherList)), max(list))

上面代碼是不是更好的一點,但一般我們沒有或很少使用強大的IDE的代碼完成功能來完成。