当想要扩展一个既有类的功能时,在Java中你能想到的方法:
除了上面说的三种方式外,在Kotlin中还提供一种新的方式:扩展(Extension)。
一、扩展函数(Extension Functions)
1. 语法形式:
fun 类名.函数名(形参列表):返回类型{
函数体
}
2. 基本示例:给String类增加一个firstChar()方法,用来返回最后一个字符
fun String.firstChar() = this.get(0)
这里的this是可以省略的,之所以写出来,是为了说明,你可以在扩展函数里使用String类中定义的已有的public方法。这里的String是待扩展的类,被称之为接收者类型(receiver type)。而调用该扩展函数的对象,就是上面中的this,被称之为接收者对象(receiver object)。
fun main(args: Array) {
println("hello world".firstChar()) //打印h
}
你看,就好像调用String类的其他实例方法那样去调用扩展函数
import firstChar
这样,实际上隐藏着一个问题。当我在第三个kt文件C中同样为String类定义了相同函数签名的扩展函数,那么在A中使用时,同样需要导入,这个时候就会产生歧义,到底是调用哪个firstChar?别着急,我们有解决办法:
import firstChar as BFirstChar
import firstChar as CFirstChar
像这样,通过as关键字对导入的扩展函数进行重命名,这样就可以区分到底是调用哪个firstChar方法了。
5. 扩展函数的解析是静态的(Extensions are resolved statically):就是说,无论你定义多少个扩展函数,并不会改变接收者类的类的结构,即不会将这些扩展函数插入到接收者类体中成为它的成员函数。
6. 扩展函数的分发也是静态的(Extensions are dispatched statically),即不具有多态性。
open class View {
open fun click() = println("View clicked")
}
class Button : View() {
override fun click() = println("Button clicked")
}
fun main(args: Array) {
val view: View = Button()
view.click()
}
当然会打印Button clicked。但是如果:
fun View.enabled(){
println("View enabled")
}
fun Button.enabled(){
println("Button enabled")
}
fun main(args: Array) {
val view: View = Button()
view.enabled() //View enabled
val button: Button = Button()
button.enabled() //Button enabled
}
到底是调用View还是Button的enabled方法,取决于引用的类型,该引用所实际指向的对象的类型。即这是一种编译期行为,而不是运行期行为。
public class ExtensionInJava {
public static void main(String[] args) {
char firstChar = ExtensionTestKt.firstChar("hello world");
System.out.println(firstChar);
}
}
这里说明一下,前面的firstChar扩展函数我是定义在ExtensionTest.kt文件中。你会发现,在Java中,调用扩展函数和调用静态方法没有什么区别,并且将第一个参数就是接收者对象。这也说明了扩展函数的本质:An extension function is a static method that accepts the receiver object as its first argument.
二、扩展属性(Extension Properties)
除了可以定义扩展函数外,属性也是可以扩展的。语法形式同扩展函数类似:
val/var 接收者类型.属性名: 属性类型
get(){}
set(){}
如:
val List.lastIndex: Int
get() = size - 1
注意,由于扩展属性并没有对应的支持字段(Backing Field)(关于Backing Field的理解,请查看我的另一篇博客),所以如果是val类型,那么必须get方法;如果是var类型,那么必须提供set、get方法。
class D{
fun bar(){}
}
class C{
fun baz(){}
fun D.foo(){
bar()
baz()
}
fun caller(d: D){
d.foo()
}
}
这里的D,被称作扩展接收者(extension receiver),而C,被称作分发接收者(dispatcher receiver)。当分发接收者和扩展接收者在调用产生冲突时,扩展接收者优先:
fun main(args: Array) {
D().foo() //编译报错
}
2. 作为类的成员的扩展是可以被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()
}
}
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())
C1().caller(D())
C().caller(D1())
}
可以思考一下,上面的代码执行后的输出结果是什么?
c. 和传入的D1()的没关系,其他的和第一条类似
参考文献:《Kotlin in Action》 kotlin官方文档