# 扩展
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` 类里面所有可能的方法,对吗?这就是扩展能帮到我们的地方。