文章19 | 阅读 10432 | 点赞0
当想要扩展一个既有类的功能时,在Java中你能想到的方法:
除了上面说的三种方式外,在Kotlin中还提供一种新的方式:扩展(Extension)。
一、扩展函数(Extension Functions)
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方法。
三、关于扩展函数几个注意点:
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
this@C.toString() //调用C中的toString
}
}
另外,对于这种定义在另一个类中的扩展函数,其只在该类中可见。例如,我们想在main函数中使用D的扩展函数foo,发现会报错,根本不认识这个foo函数,即不可见。
fun main(args: Array<String>) {
D().foo() //编译报错
}
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官方文档
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/xlh1191860939/article/details/79625234
内容来源于网络,如有侵权,请联系作者删除!