Kotlin Reference: Extensions
Kotlin 提供了类似 C# 和 Gosu 中对类进行扩展的功能,可以在不继承类或使用特定设计模式(如修饰者)的情况下,为类添加新的功能。这是通过名为扩展(Extensions)的特殊声明实现的。Kotlin 支持扩展函数和扩展属性。
Contents
Extension Functions
通过在函数名前添加接收者类型(Receiver Type,即被扩展的类型),可以声明扩展函数。如下面为 MutableList<Int>
添加了一个 swap()
函数:
[code lang=”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
}[/code]
这里在扩展函数中的 this
关键字表示接收者对象(也就是函数名 .
前面的类型),之后就可以在任意 MutableList<Int>
上调用 swap()
了:
[code lang=”kotlin”]val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // ‘this’ inside ‘swap()’ will hold the value of ‘l'[/code]
当然,这个函数对于任何 MutableList<T>
都有用,我们可以使用泛型:
[code lang=”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
}[/code]
我们在函数名前先声明了泛型参数,这样才能在接收者类型表达式中使用该泛型,详见 Generic functions。
Extensions are resolved statically
扩展实际上并不会修改被扩展的类。定义扩展时,不会向被扩展的类注入新的成员,而只是添加了一个可以在该类型变量上用点标记(.
)访问的函数。
需要强调的是,扩展函数的分发是静态的,也就是说,它们并不是其接收类型的虚函数。这意味着扩展函数的调用取决于调用扩展函数的表达式的类型,而不是表达式在运行时的结果。举例来说:
[code lang=”kotlin”]open class C
class D: C()
fun C.foo() = “c”
fun D.foo() = “d”
fun printFoo(c: C) {
println(c.foo())
}
printFoo(D())[/code]
上面的例子会输出 c
,因为调用哪个扩展函数取决于参数 c
的声明类型,也就是类 C
。
如果为类定义的扩展函数和其已有的成员函数具有相同的名称,且都适用于给定参数,则成员函数总会胜出,例如:
[code lang=”kotlin”]class C {
fun foo() { println(“member”) }
}
fun C.foo() { println(“extension”) }[/code]
在 C
的任意实例 c
上调用 c.foo()
,会输出 member
而不是 extension
。
但扩展函数可以重载类的成员函数,即具有相同的函数名和不同的签名:
[code lang=”kotlin”]class C {
fun foo() { println(“member”) }
}
fun C.foo(i: Int) { println(“extension”) }[/code]
调用 C().foo(1)
会输出 extension
。
Nullable Receiver
注意扩展可以定义在可为空(Nullable)的接收类型上,此类扩展可以在为 null
的对象上调用,然后在扩展体内进行 this == null
的检查。这也是 Kotlin 允许在不进行 null
检查的情况下调用 toString()
的原因:检查发生在扩展函数内。
[code lang=”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()
}[/code]
Extension Properties
类似于扩展函数,Kotlin 也支持扩展属性:
[code lang=”kotlin”]val <T> List<T>.lastIndex: Int
get() = size – 1[/code]
注意由于扩展不会向被扩展的类插入成员,并不能让扩展属性具有支持字段(Backing Field),所以扩展属性不能有初始化,只能通过显式提供 getter 和 setter 来定义扩展属性的行为。
例子:
[code lang=”kotlin”]val Foo.bar = 1 // error: initializers are not allowed for extension properties[/code]
Companion Object Extensions
如果一个类定义了伴生对象(Companion Object),则也可以为伴生对象定义扩展函数和扩展属性:
[code lang=”kotlin”]class MyClass {
companion object { } // will be called “Companion”
}
fun MyClass.Companion.foo() {
// …
}[/code]
只需使用类名来调用伴生对象扩展,就像调用伴生对象的普通成员一样:
[code lang=”kotlin”]MyClass.foo()[/code]
Scope of Extensions
多数情况下我们把扩展定义在顶层,即直接在包里面:
[code lang=”kotlin”]package foo.bar
fun Baz.goo() { … }[/code]
如果要在声明扩展的包外使用该扩展,需要在调用处导入:
[code lang=”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()
}[/code]
更多信息请参考导入。
Declaring Extensions as Members
可以在一个类内部为另一个类声明扩展,在这种情况下,扩展具有多个隐式的接收者——扩展可以直接访问这些接收者的成员而不必使用限定符(Qualifier)。扩展声明所在的类的实例称为分发接收者(Dispatch Receiver),扩展方法的接收者类型的实例称为扩展接收者(Extension Receiver)。
[code lang=”kotlin”]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
}
}[/code]
如果分发接收者和扩展接收者具有成员出现命名冲突,则扩展接收者优先,此时如果要访问分发接收者的成员,需要使用 限定的 this
语法。
[code lang=”kotlin”]class C {
fun D.foo() {
toString() // calls D.toString()
this@C.toString() // calls C.toString()
}[/code]
声明为成员的扩展可以声明为 open
并被子类覆盖,这意味着此类扩展函数的分发对于分发接收者类型是虚拟的,但对于扩展接收者是静态的。
[code lang=”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() // 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[/code]
Motivation
在 Java 中,我们经常使用如 FileUtils
、StringUtils
等以 “*Utils” 的命名工具类,最著名同类的要数 java.util.Collections。工具类不方便的地方在于调用的代码会写成这样:
[code lang=”java”]// Java
Collections.swap(list, Collections.binarySearch(list,
Collections.max(otherList)), Collections.max(list))[/code]
这些类名总是很碍事,使用静态导入可以得到:
[code lang=”java”]// Java
swap(list, binarySearch(list, max(otherList)), max(list))[/code]
这样稍微好一点,但牺牲了 IDE 强大的的代码完成功能。如果能像这样调用是最好的:
[code lang=”java”]// Java
list.swap(list.binarySearch(otherList.max()), list.max())[/code]
但我们又不想实现 List
类中的全部方法,这时就是扩展大显身手的时候了。