Kotlin学习系列之:扩展(Extension)

x33g5p2x  于2022-03-08 转载在 其他  
字(3.3k)|赞(0)|评价(0)|浏览(505)

当想要扩展一个既有类的功能时,在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
this@C.toString() //调用C中的toString
}
}
    另外,对于这种定义在另一个类中的扩展函数,其只在该类中可见。例如,我们想在main函数中使用D的扩展函数foo,发现会报错,根本不认识这个foo函数,即不可见。

fun main(args: Array<String>) {
    D().foo()    //编译报错
}
  1. 作为类的成员的扩展是可以被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官方文档

相关文章