当想要扩展一个既有类的功能时,在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)。
3. 在Kotlin中使用:
fun main(args: Array<String>) { println("hello world".firstChar()) //打印h }你看,就好像调用String类的其他实例方法那样去调用扩展函数
4. 当你在一个kt文件A中使用定义在其他kt文件B中的扩展函数时,需要使用import语句进行导入。
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<String>) { val view: View = Button() view.click() }当然会打印Button clicked。但是如果:
fun View.enabled(){ println("View enabled") } fun Button.enabled(){ println("Button enabled") } fun main(args: Array<String>) { val view: View = Button() view.enabled() //View enabled val button: Button = Button() button.enabled() //Button enabled }到底是调用View还是Button的enabled方法,取决于引用的类型,该引用所实际指向的对象的类型。即这是一种编译期行为,而不是运行期行为。
7. 在Java代码中如何去调用Kotlin中定义的扩展函数:
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 <T> List<T>.lastIndex: Int get() = size - 1注意,由于扩展属性并没有对应的支持字段(Backing Field)(关于Backing Field的理解,请查看我的另一篇博客),所以如果是val类型,那么必须get方法;如果是var类型,那么必须提供set、get方法。
三、关于扩展函数几个注意点:
1. 当扩展函数定义在某个类中,即作为类的成员:
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)。当分发接收者和扩展接收者在调用产生冲突时,扩展接收者优先:
class C{
fun D.foo(){
toString() //调用的是D中的toString
[email protected]() //调用C中的toString
}
}
另外,对于这种定义在另一个类中的扩展函数,其只在该类中可见。例如,我们想在main函数中使用D的扩展函数foo,发现会报错,根本不认识这个foo函数,即不可见。
fun main(args: Array<String>) { 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<String>) { C().caller(D()) C1().caller(D()) C().caller(D1()) }可以思考一下,上面的代码执行后的输出结果是什么?
D.foo in C
D.foo in C1
D.foo in C
和你想的一样吗?
我们来一条条地解释一下:
首先明确一下,C中的caller方法是public,被C1给继承了下来。然后来看第一条:
a. C的caller方法里会调用D的foo方法,而C中的D.foo()会输出”D.foo in C”
b. C1的caller方法里会调用D的foo方法,而C1中的D.foo()已经将C中的D.foo重写,此时会输出“D.foo in C1”
c. 和传入的D1()的没关系,其他的和第一条类似
参考文献:《Kotlin in Action》 kotlin官方文档