Kotlin与C#和Gosu类似,可以为一个类扩展一个新功能,但是不必继承该类或使用设计模式,如装饰器模式。Kotlin是通过被称为扩展的特殊声明完成这项工作的。Kotlin支持函数扩展和属性扩展。
函数扩展(Extension Functions)
为了声明一个扩展函数,我们需要以接收者类型作为该函数的前缀,也就是说:接收者类型就是被扩展的类型。如下示例了为MutableList
增加了一个swap
方法:
fun MutableList.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
上述扩展函数的函数体内部,this关键字代表着被扩展类型的对象(.
操作符前的对象)。现在,我们可以使用MutableList
类型的任意对象调用已经扩展的函数:
val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 'this' inside 'swap()' will hold the value of 'l'
当然,如果我们为其加上泛型,则该函数对于任何MutableList
fun MutableList.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
我们在扩展函数名称之前声明泛型参数,使其在目标接收类型表达式中可用。
扩展被静态解析(Extensions are resolved statically)
扩展的实现并没有真实的修改被扩展的类。扩展的定义,并没有在目标类中添加一个新的成员,仅是可以使目标类型的对象通过.
操作符调用扩展函数而已。
要强调的是扩展函数是被静态调用的,也就是说它们并非目标类型的真实方法。意思就是说被调用的扩展函数被函数体调用的表达式类型决定(形参类型),而非运行时看到的实际传入类型。例如:
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(Nullable Receiver)
请注意,可以使用可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()
}
属性扩展(Extension Properties)
与函数扩展类似,Kotlin支持属性扩展:
val List.lastIndex: Int
get() = size - 1
由于扩展的实现并没有在目标类中插入真实的成员,因此不能够让一个扩展属性拥有一个备份字段(backing field)。这就是为什么初始化块不允许初始化扩展属性。它们只能通过显式提供getter/setter方法来操作。例如:
val Foo.bar = 1 // error: initializers are not allowed for extension properties
伴生对象扩展(Companion Object Extensions)
如果一个类定义了伴生对象,则可以为该伴生对象扩展函数或属性:
class MyClass {
companion object { } // will be called "Companion"
}
fun MyClass.Companion.foo() {
// ...
}
与伴生对象的常规成员类似,只能使用类名作为限定词来调用:
MyClass.foo()
范围扩展(Scope of Extensions)
大多数情况下,我们定义扩展都是直接在包下的最高层级进行:
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()
)
声明扩展作为成员(Declaring Extensions as Members)
在一个类中,您可以为另一类声明扩展。在这样的扩展中,将存在多个隐式接收器 - 可以在没有限定符的情况下就访问到的对象成员。 声明扩展的类的实例称为调用接收者,扩展方法的接收类型称为扩展接收者。
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
}
}
在调用接收者的成员与扩展接收者成员之间发生命名冲突的情况下,扩展接收者优先。要引用调用接收者的成员,可以使用如下语法:
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
扩展的设计目的(Motivation)
在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())
但是我们在List
类中实现所可能的方法,对吧?这就是扩展帮助我们的地方。