Kotlin 無需繼承實現對類的功能擴展

zhf123 7年前發布 | 14K 次閱讀 Kotlin

Kotlin 同 C# 和 Gosu 類似,能夠擴展一個類的新功能而無需繼承該類或使用像裝飾者這樣的任何類型的設計模式。 這通過叫做_擴展_的特殊聲明完成。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 類。

如果一個類定義有一個成員函數和一個擴展函數,而這兩個函數又有相同的接收者類型、相同的名字 并且都適用給定的參數,這種情況 總是取成員函數 。 例如:

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

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

如果我們調用 C 類型  c 的  c.foo() ,它將輸出“member”,而不是“extension”。

當然,擴展函數重載同樣名字但不同簽名成員函數也完全可以:

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

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

調用 C().foo(1) 將輸出 "extension"。

可空接收者

注意可以為可空的接收者類型定義擴展。這樣的擴展可以在對象變量上調用, 即使其值為 null,并且可以在函數體內檢測 this == null ,這能讓你 在沒有檢測 null 的時候調用 Kotlin 中的toString():檢測發生在擴展函數的內部。

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

注意:由于擴展沒有實際的將成員插入類中,因此對擴展屬性來說 幕后字段 是無效的。這就是為什么 擴展屬性不能有 初始化器 。他們的行為只能由顯式提供的 getters/setters 定義。

示例:

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.example.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()
        this@C.toString()  // 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”: FileUtils 、 StringUtils 等,著名的  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 強大的自動補全功能中得到幫助。如果能這樣就更好了:

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

這會變得好一點,但是我們并沒有從 IDE 強大的自動補全功能中得到幫助。如果能這樣就更好了:

 

來自:https://coyee.com/article/12205-kotlin-extensions

 

 本文由用戶 zhf123 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!