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
」的類: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的代碼完成功能來完成。