1、什么是扩展
类似于C#
和 Gosu
,Kotlin
提供了向一个类扩展新功能的能力, 而不必继承这类, 也不必使用像装饰者模式的任何设计模式。 这种功能是通过一种特殊的声明来实现的, Kotlin 中称为 扩展(extension).
Kotlin 支持 扩展函数(extension function) 和 扩展属性(extension property)。
2、扩展函数
要声明一个扩展函数, 我们需要在函数名之前添加前缀, 表示这个函数的 接收者类型(receiver type), 表明我们希望扩展的类型。如下,为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
关键字 指 的是接收者类型(receiver type, 即MutableList<Int>
,.
之前所对应的类型) 。现在我们可以对 任意 MutableList<Int>
调用 swap
函数:
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
}
2.1、扩展是静态解析的
扩展实际上不会修改它们所扩展的类。通过定义一个扩展,不会向类中添加新函数,而是使用这种类型的变量通过.
来调用新功能。
强调一点,扩展函数是静态分发的,即它们不是虚拟的,是依赖接收者类型的。这意味着 扩展函数的调用是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的。例:
open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
printFoo(D()) // result is "c”
打印的结果是“c”.因为最终调用的是 c 的扩展函数。
如果类中存在成员函数, 同时又在同一个类上定义了同名的扩展函数, 并且与调用时指定的参数匹配, 这种情况下 总是会优先使用成员函数。
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }
如果调用 c.foo()
,打印的结果是“member”.
不过,如果扩展函数复写了同名的成员函数,但是具有不同的参数,这完全是可以的。根据参数调用不同的函数即可:
class C {
fun foo() { println("member") }
}
fun C.foo(i: Int) { println("extension") }
如果调用 c.foo()
,打印的结果是“extension”.
2.2、可空接收类型
扩展可以被定义成一个可空的接收类型。这样的扩展可以用 对象变量来调用,即使它的值为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()
}
3、扩展属性
与扩展函数类似, Kotlin 也支持扩展属性。
val <T> List<T>.lastIndex: Int
get() = size - 1
注意 : 由于扩展属性实际上不会向类添加新的成员, 因此无法让一个扩展属性拥有一个 幕后属性。 所以, 对于扩展属性不允许存在初始化. 扩展属性的行为只能通过明确给定的取值方法与设值方法来定义。
// error: initializers are not allowed for extension properties
val Foo.bar = 1
4、伴随对象(companion object)扩展
对于伴随对象,也支持扩展函数和扩展属性。
class MyClass {
companion object { } // will be called "Companion"
}
fun MyClass.Companion.foo() {
// ...
}
和 伴随对象中的普通成员一样,扩展函数和扩展属性的也只能由 类名调用:
MyClass.foo()
5、扩展区间
扩展函数通常定义成顶层函数:
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()
}
6、将扩展声明为成员(定义在类内部,以限制其作用域。)
在一个类中,可以声明另一个类的扩展。在这样的扩展内,有多个隐式接收者- 这些接收者的对象成员可以不用限定符就可以被访问。
声明扩展的类的实例被称为 分发接收者(dispatch receiver
),而扩展函数的接收者类型所对应的类的实例被称为 扩展接收者(extension receiver
):
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@ClassName”方式:
class C {
fun D.foo() {
toString() // calls D.toString()
this@C.toString() // 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
7、动机(Motivation)
在java 中,我们通常将类命名为Utils": FileUtils, StringUtils
等等。很有名的“java.util.Collections”也是这样命名的。令人不愉快部分是,使用这些工具类的代码像这样:
// 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 中所有可能的方法,对吧? 扩展刚好可以帮助到我们。