本文主要介绍 Kotlin
扩展 ( Extension
)的相关内容。
以下翻译自官方文档:
与
C#
和Gosu
类似,Kotlin
提供了向一个类扩展新功能的能力, 而且不必从这个类继承,也不必使用任何设计模式, 比如Decorator
模式之类。 这种功能是通过一种特殊的声明来实现的,Kotlin
中称为 扩展(extension
)。Kotlin
支持 扩展函数(extension function
) 和 扩展属性(extension property
)。
扩展 是很多高级语言都具备的特性,使用扩展可以在不侵入原来类的基础上扩展新的功能,比如我们常用的 Utils
方法,就可以完全使用扩展方法来替代。
扩展函数
扩展函数需要声明在top-level
级别下,如果声明在一个类里面,那么只能在这个类内使用,也就失去了扩展的意义。我开始写的时候因为写 java
的惯性,就声明在类里面,结果别的地方根本用不了,一度十分怀疑扩展存在的意义。。。
声明的方式与普通函数稍有不同,在函数名前面要使用扩展的接收者,扩展的接收者就是我们要扩展的对象,如下:
1 | fun 扩展接收者.函数名:返回类型(参数列表){ |
先来声明两个扩展函数看看效果
1 | // ExtensionTest.kt |
在 MainActivity.kt
中使用它们
1 | extensionLog("测试扩展函数") |
输出结果:
1 | com.march.ktexample E/MainActivity: 测试扩展函数 |
总结一下:
扩展函数的声明对子类是有效的, 比如我们给
Context
类扩展,那么Context
所有子类都可以使用扩展函数,扩展虽然不是继承,但是却具有和继承类似的效果。在扩展函数中可以使用
this
关键字,访问当前扩展接收者对象。
扩展函数的静态解析
扩展函数并不是真的给扩展接收者的类增加了新的方法,他只是创建了一个新的函数,并且可以让扩展接收者的类使用点号的形式去调用而已,对原来的类本身没有任何影响。
扩展函数的调用派发过程是静态的,这就意味着,调用扩展函数时,具体被调用的函数是哪一个,是通过调用函数的对象表达式的类型来决定的,而不是在运行时刻表达式动态计算的最终结果类型决定的。也就是说如果有多个扩展调用,那么调用哪一个取决于你使用哪种类型去调用。
1 | open class A |
输出结果
1 | com.march.ktexample E/MainActivity: a的扩展 |
成员函数优先
当扩展函数和成员函数都存在时,相同的函数是说函数名与参数列表都相同,优先使用成员函数。
1 | // B 有成员函数 |
可为空的接收者
使用可为空的类型作为扩展接收者时,即使调用的对象为空仍然可以调用,不会空指针,可以在扩展方法内检测接受者是不是为空。
1 | // 可为空的接收者 |
再看一下,可空类型的 toString()
方法
1 | fun Any?.toString(): String { |
测试
1 | val strNull1:String? = null |
扩展属性
扩展属性并不真的是类的成员,他不能更改类的内容,因此扩展属性不支持 backing-field
,因此没办法使用 set()
为属性赋值。
扩展属性不能使用 初始化器 为属性赋值
1 | var Context.extensionProperty: String |
扩展同伴对象
1 | class MainActivity : Activity() { |
声明扩展为成员
以下翻译自官方文档:在类的内部,你可以为另一个类定义扩展。在这类扩展中,存在多个 隐含接受者(implicit receiver
) - 这些隐含接收者的成员可以不使用限定符直接访问。扩展方法的定义所在的类的实例,称为 派发接受者(dispatch receiver)
, 扩展方法的目标类型的实例,称为 扩展接受者(extension receiver)
。
也就是说我们可以在一个类 BB 内部声明另一个类 AA 的扩展,AA 是扩展接受者,BB 是派发接受者,此时存在多个接受者,在 AA 的扩展方法中,可以自由的不使用限定符的访问 AA 和 BB 的成员。
1 | class AA { |
当派发接受者与扩展接受者的成员名称发生冲突时,扩展接受者的成员将会被优先使用。 如果想要使用派发接受者的成员,需要使用 this
关键字
1 | class BB { |
扩展函数可以在子类中被覆盖,这类扩展函数在派发过程中针对派发接受者是虚拟的,针对扩展接受者是静态的。
前面说过,扩展的解析是静态的,使用哪个扩展取决于调用他的是哪个类型的类对象,但是派发接受者是虚拟的是说,他将使用子类中更具体的实现。
看一个官网的例子,下面的 caller(d:D)
方法接收的是 D 类型的对象,此时调用扩展函数时,不管你传递什么类型的值(D 或 D 的子类对象),都会调用 D 的扩展函数,因为扩展接受者的解析是静态的, 所以所有的打印结果都是 D.foo
;但是派发接受者的解析是虚拟的,所以 C1().caller()
将使用子类中更具体的实现,C1 对扩展函数进行了重载,因此会使用 C1 重载后的 caller()
打印出 in C1
。
1 | open class D { |
总结
扩展声明在 top-level
才能在所有地方都可以使用。
扩展函数的声明对子类是有效的,我们扩展父类,子类也会有这些扩展。
在扩展函数中可以使用 this
关键字,访问当前扩展接收者对象。
扩展的的接受者可以为空,不会 NPE
。
成员函数和扩展函数冲突时,成员函数优先。
扩展接受者的解析是静态的,将会使用哪个扩展取决于调用的类的类型,也就是说类型确定了,使用的方法就确定了,并不在乎实际上是什么类型,比如我参数是 d:D
,就决定了是 D
类型的扩展方法,传它的子类 D1:D
类型也不会改变。
派发接受者的解析是虚拟的,将会使用哪个扩展取决于调用的类的实际的类型,将会使用当前实际类型重载的方法,比如我参数是 d:D
,但我传它的子类 D1:D
类型则会使用 D1
中对扩展的重载。