Kotlin 与C# 和Gosu 类似,提供了无需必须继承类,或者使用装饰器模式来扩展类功能的能力。这是通过一种叫做扩展的extension
特殊的声明来实现的。Kotlin支持扩展方法和扩展属性。
声明一个扩展方法,我们需要在它的名字前面加上一个接收类型前缀,例如,加上一个被扩展的类型。如下示例给MultableList
添加了一个swap
方法:
fun MutableList.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' 指代list
this[index1] = this[index2]
this[index2] = tmp
}
在扩展方法中,关键字this
指代接收对象,也就是’.’之前的调用者。 现在演示如何调用MutableList
的扩展方法:
val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 'this' 会指代 'l'
当然,这个函数对于任何类型的MutableList
都有意义,所以可以把它泛型化:
fun MutableList.swap(index1: Int, index2: Int) {
val tmp = this[index1]
this[index1] = this[index2]
this[index2] = tmp
}
我们在函数名的前面为它声明了泛型类型参数,这样使得可以在接收类型中使用泛型。
扩展实际没有修改被扩展的类。定义一个扩展时,并没有插入在类中插入新的成员,而仅仅是让新函数可以通过点操作符和该类型的变量调用。
这里要强调一下,扩展方法是静态的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
,可以在扩展函数体内通过this == null
来判断是否为空。这种方式使得在koglin调用toString()
的时候不用检查是否为空null
,转而在扩展函数里面去检查。
fun Any?.toString(): String {
if (this == null) return "null"
return toString()
}
和扩展函数类似,Kotlin支持扩展属性
val <T> List<T>.lastIndex: Int
get() = size - 1
注意,因为扩展并不会在类中真正添加成员,因此扩展属性并没有一个有效的方式拥有backing field
。 这也是为什么对于扩展属性不支持初始化器的原因。他们的行为只能通过显式地提供getter/setter 来定义。例如
val Foo.bar = 1 // error: initializers 对于扩展属性不支持
如果一个类定义了伙伴对象companion object
,你同样可以定义这个伙伴对象的扩展函数和扩展属性:
class MyClass {
companion object { }
}
fun MyClass.Companion.foo() {
}
和伙伴对象的普通成员一样,他们可以使用类名来调用扩展函数。
MyClass.foo()
通常情况我们会在顶层定义扩展,例如直接在包下定义。
package foo.bar
fun Baz.goo() { }
要在定义这个扩展的包外面来使用这个扩展函数,我们需要导入这个包。
package com.example.usage
import foo.bar.goo
import foo.bar.*
fun usage(baz: Baz) {
baz.goo()
}
在类中,可以定义另外一个类的扩展。 在这样的扩展中,会有多个隐式接收者(不需要限定符可以直接访问的成员)。这样一个定义了其他类的扩展的类的实例叫做dispatch receiver
,并且扩展函数接收者类型的实例叫做extension receiver
。
class D {
fun bar() { }
}
class C {
fun baz() { }
fun D.foo() {
bar() // 调用 D.bar
baz() // 调用 C.baz
}
fun caller(d: D) {
d.foo() // 调用扩展函数
}
}
被定义成成员的扩展可以声明为open
并且在子类中被复写。 这意味着分发这样的函数调用时对于dispatch receiver
类型是虚的,对于extension receiver
类型来说是静态的。
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")
}
}
fun main(args: Array) {
C().caller(D())
C().caller(D1())
C1().caller(D())
C1().caller(D1())
/** 输出
D.foo in C
D.foo in C
D.foo in C1
D.foo in C1
*/
}
java中,我们习惯使用这样的类”*Utils”: FileUtils
,StringUtils
等等。著名的Java.util.Collections
就属于这类。使用这些util类会让代码长成这样:
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list))
这些类名一直是这样的。我们可以使用静态引用:
swap(list, binarySearch(list, max(otherList)), max(list))
这样稍微好一些,但是这样对我们使用IDE强大的提示功能一点帮助也没有。 如果可以这样的话会好很多:
list.swap(list.binarySearch(otherList.max()), list.max())
但是我们不想在List
中实现所有这些可能的方法。因此,这就是扩展能够帮助我们的地方。
PS,我会坚持把这个系列写完,有问题可以留言交流,也关注专栏Kotlin for Android Kotlin安卓开发