Kotlin 扩展函数的局限与注意点总结

一、扩展方式对扩展函数的影响

Kotlin是一种静态类型语言,我们创建的每个对象不仅具有运行时,还具有编译时类型,开发人员必须明确指定(在Kotlin中可以判断)。在使用扩展函数时,要清楚地了解静态调用和动态调用之间的区别。

1.静态与动态调度

public class Base {

    public void payfoo() {
        System.out.println("I am Base payFoo!");
    }
}

public class Extended extends Base {

    @Override
    public void payfoo() {
        System.out.println("I am Extended payFoo!");
    }
}

public class DoExecute {

    public static void main(String[] args) {
        Base base = new Extended();
        base.payfoo();
    }
}

I am Extended payFoo!

 对于以上代码,我们声明一个名为base的变量,它具有编译时类型Base和运行时类型Extended。当我们调用时,base.payFoo()将动态调度方法,这意味着运行时类型是Extended。

当我们调用重载方法时,这种情况下,因为base本质上是Extended的实例,最终会执行Extended方法。

public static void main(String[] args) {
    Base base = new Extended();
    payFoo(base);
}

public static void payFoo(Base base) {
    base.payfoo();
}

public static void payFoo(Extended extended) {
    extended.payfoo();
}

I am Extended payFoo!

二、扩展函数始终静态调度

扩展函数都有一个接收器(receiver),由于接收器实际上只是字节代码中编译方法的参数,因此可以重载它,但不能覆盖它,这可能就是成员和扩展函数之间的最重要区别:前者是动态调度的,后者总是静态调度的。

例子:

open class Base

class Extended : Base()

fun Base.foo() = "I am Base.foo"

fun Extended.foo() = "I am Extended.foo"

fun main() {
    val instance: Base = Extended()
    val instance2: Extended = Extended()
    println(instance.foo())
    println(instance2.foo())
}

I am Base.foo
I am Extended.foo

从上面发现扩展只考虑编译时类型。

 

三、类中的扩展函数

如果在类内部声明扩展函数,那么它将不是静态的。如果将扩展函数加上open关键字,我们就可以在子类中进行重写(override)。即当在类内部声明扩展函数时,它同时具有调度接收器和扩展接收器。

调度接收器和扩展接收器的概念

扩展接收器(extension receiver):与Kotlin扩展密切相关的接收器,表示我们为其定义扩展的对象。

调度接收器(dispatch receiver):扩展被声明为成员时存在的一种接收器,它表示声明扩展名的类的实例。

例子:

class Y {

}

class X {
    fun Y.foo() = "I am foo"
}

 在上面的代码中,X是调度接收器,而Y是扩展接收器。如果将扩展函数声明为open,则它的调度接收器只能是动态的,而扩展接收器总是在编译时解析。

open class PayBase

class PayExtend : PayBase()

open class PayX {
    open fun PayBase.foo() {
        println("I am PayBase.foo in X")
    }

    open fun PayExtend.foo() {
        println("I am PayExtend.foo in X")
    }

    fun deal(base: PayBase) {
        base.foo()
    }
}

class PayY : PayX() {
    override fun PayBase.foo() {
        println("I am PayBase.foo in Y")
    }

    override fun PayExtend.foo() {
        println("I am PayExtend.foo in Y")
    }
}

fun main() {
    PayX().deal(PayBase())
    PayY().deal(PayBase())

    PayX().deal(PayExtend())
    PayY().deal(PayExtend())
}

I am PayBase.foo in X
I am PayBase.foo in Y  //dispatch receiver 被动态处理
I am PayBase.foo in X //extension receiver 被静态处理
I am PayBase.foo in Y

 通过上面代码可以发现,PayExtend()扩展函数始终没有被调用。并且决定两个PayBase的扩展函数foo执行哪一个,直接因素是执行deal方法的类的运行时类型。

 

 

总结几个扩展函数需要注意的地方:

1.如果该扩展函数是顶级函数或成员函数,则不能被覆盖。

2.我们无法访问其接收器的非公共属性。

3.扩展接收器总是被静态调度。

 

 

参考:Kotlin核心编程

 

 

 

 

 

 

 

你可能感兴趣的:(Kotlin)