# 擴展
Kotlin 類似于 C# 和 Gosu 提供的在不繼承類或使用如“裝飾模式”一樣的設計模式的情況下利用新功能擴展現有類的功能。通過稱為_擴展_的特殊的聲明。Kotlin 支持_擴展函數_和_擴展屬性_。
## 擴展函數
要聲明擴展函數,我們需要在前面加上一個接受類型,意即要擴展這個類型。下面的代碼添加一個 `swap` 函數到 `MutableList<Int>`:
``` kotlin
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>` 上調用這個函數:
``` kotlin
val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'l'
```
當然,這個函數在任何 `MutableList<T>` 中也講得通,并且我們可以把它做成泛型:
``` kotlin
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
}
```
為了在接收者類型表達式中能獲得泛型,我們在函數名之前我們聲明了泛型類型參數。查看[泛型函數](generics.html)
## 擴展是**靜態地**解析
事實上擴展并沒有更改類。定義一個擴展,你并非在類中插入一個新成員,只不過使用新函數可以在這個類實例上隨點標記被調用。
我們要強調擴展函數是**靜態地**處理,意即它們對于接受類型并不可見。這個意思是擴展函數的調用由要調用的函數表達式類型指明,而不是在運行時通過評估表達式結果的類型。例如:
``` kotlin
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`。
如果某個類有一個成員函數,而某個擴展函數又定義了相同的接受類型、相同的名稱而產生了沖突,那么**成員總是勝出**。例如:
``` kotlin
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }
```
如果我們調用 `C` 類型任何 `c` 的 `c.foo()`,那么它將會打印 “member”,而非 “extension”。
## 可空接受者
注意擴展可以定義為一個可為空的接受類型。這樣擴展可以在一個對象變量上被調用即即使它的值為 null,并可以在內部檢查 `this == null`。這允許你在 Kotlin 中調用 toString() 而不用檢查 null:該檢查在擴展函數內部發生。
``` kotlin
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 支持擴展屬性:
``` kotlin
val <T> List<T>.lastIndex: Int
get() = size - 1
```
注意,由于擴展事實上并非插入成員到類里面,所以沒有高效的方法讓擴展屬性有一個[后臺字段](properties.html#backing-fields)。這就是為什么**擴展屬性不允許有初始器**。它們的行為只能通過明確地提供 getter/setter 來定義。
示例:
``` kotlin
val Foo.bar = 1 // error: initializers are not allowed for extension properties
```
## 伙伴對象擴展
如果某個類定義了[伙伴對象](object-declarations.html#companion-objects),你還可以為這個伙伴對象定義擴展函數和屬性:
``` kotlin
class MyClass {
companion object { } // will be called "Companion"
}
fun MyClass.Companion.foo() {
// ...
}
```
就像該伙伴對象的普通成員一樣,它們只能使用類名限定符調用:
``` kotlin
MyClass.foo()
```
## 擴展的作用域
大多數時候我們在頂層定義擴展,意即直接在包下面:
``` kotlin
package foo.bar
fun Baz.goo() { ... }
```
要在聲明它的包以外使用這種擴展,我們需要在調用地方導入它:
``` kotlin
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()
)
```
更多信息查看 [導入](packages.html#imports)。
## 聲明擴展為成員
你可以在一個類里為其它類聲明擴展。在這種擴展里有多個_隱含接收者_ —— 可以不用限定符訪問的對象成員。聲明這個擴展的類的實例叫做_發送接受者_,而這個擴展方法的接受類型的實例稱做_擴展接受者_.
``` kotlin
class D {
fun bar() { ... }
}
class C {
fun baz() { ... }
fun D.foo() {
bar() // 調用 D.bar
baz() // 調用 C.baz
}
fun caller(d: D) {
d.foo() // 調用擴展函數
}
}
```
在發送接受者的成員和擴展接受者的名稱之間發生沖突的情況下,擴展接受者獲得優先權。要關聯到發送接受者的成員,你可以使用[退定 `this` 語法](this-expressions.html#qualified)。
``` kotlin
class C {
fun D.foo() {
toString() // calls D.toString()
this@C.toString() // calls C.toString()
}
```
被聲明為成員的擴展可以聲明為 `open` 并可以被子類覆蓋。這意思是說這種函數的調度會對考慮到發送接受者的類型生效,但會以靜態考慮擴展接受者的類型。
``` kotlin
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")
}
}
C().caller(D()) // 打印 "D.foo in C"
C1().caller(D()) // 打印 "D.foo in C1" - 發送接受者被確定為有效
C().caller(D1()) // 打印 "D.foo in C" - 擴展接受者被確定為靜態
```
## 動機
在 Java 中,我們會用名為 “\*Utils” 的類:`FileUtils`、`StringUtils`等等。著名的 `java.util.Collections` 亦屬此類。而且這種類最讓人蛋疼的地方就是使用它們的代碼看上去類似這樣:
``` java
// Java
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list))
```
那些類的名字始終這樣寫。我們可以使用靜態導入:
``` java
// Java
swap(list, binarySearch(list, max(otherList)), max(list))
```
這樣稍微好一點點,但我們無法(或是一點點)從 IDE 的代碼完成中得到幫助。如果我們這樣的話也許會好很多
``` java
// Java
list.swap(list.binarySearch(otherList.max()), list.max())
```
但我們不可能實現 `List` 類里面所有可能的方法,對嗎?這就是擴展能幫到我們的地方。