【Kotlin篇】聊一聊Kotlin的扩展函数

2,394 阅读5分钟

一、何为扩展函数

就一句话

不改变原有类的情况下,扩展新的功能。

对于Java来说,扩展函数这一特性是没有的,但是Java中还是具体相同功能的特性,比如说继承,设计模式中的装饰模式。就功能来说,Kotlin中的扩展函数与之是一致的,但是Kotlin原生就自带此特性,使用也更加简练。

Kotlin的扩展函数该如何使用?

3f7a08ff7b614c9d97248198b130a0d4_tplv-k3u1fbpfcp-watermark.jpg

创建一个普通的类DogKt,类里面有两个已经存在的方法,run()和cry()。

class DogKt{
    fun run() = "狗会跑"
    fun cry() = "狗会汪汪"
}

狗狗本身就有跑和叫两个技能,而现在需要扩展它听从指令的技能,那就用扩展函数来进行扩展。在需要被扩展的类的后面,添加一个方法即可,如下:

fun DogKt.order() = "扩展功能-》狗听从指令"

创建好了基础类和扩展方法,如果需要调用狗狗的'听从指令'的功能,则直接DogKt.order()。

class DogKt{
    fun run() = "狗会跑"
    fun cry() = "狗会汪汪"
}
fun DogKt.order() = "扩展功能-》狗听从指令"

fun main(args: Array<String>) {
    var ex = DogKt()
    println(ex.run())
    println(ex.cry())
    println(ex.order)
}

结果:

image.png

二、扩展函数的解析为静态的

扩展函数的解析为静态的这句话的意思是指Kotlin的扩展函数扩展了类的功能,但是并没有改变原来类的整体结构。

以上面狗狗的例子来说,DogKt类不会因为扩展了order()功能,而多出一个order()方法,原来DogKt类中只有run()和cry()两个方法,还是只有这两个方法。

为什么这么说?验证一下,我们可以尝试看看使用扩展函数后DogKt类的字节码。

image.png 上图显而易见的,DogKt类中只有1.run(),2.cry()和3.DogKt的构造方法。 扩展的order()不在DogKt类中。

这就是,扩展函数虽然会扩展一个类的功能,但是这新功能并不属于这个类,只是存在一个引用的关系。

三、扩展函数不支持多态

我们可以先看看Java中的多态

这里有一个父类Aninal,里面存在一个run()方法,一个子类Dog继承Animal,类里面同样有一个run()方法,另外有一个调用类Person,存在一个call(Animal animal)。

//父类
public class Animal1 {
    public String run() {
        return "Animal run";
    }
}

//子类
public class Dog extends Animal1 {
    public String run() {
        return "Dog run";
    }
}

//第三个调用类
public class Person {
    public String call(Animal1 animal1) {
        return animal1.run();
    }
    
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person.call(new Animal1()));
        System.out.println(person.call(new Dog()));
    }
}

这个时候通过Person类分别来传入Animal和Dog的实例,都调用run()方法,可以得出以下结果:

image.png 分别打印出了Animal和Dog类的结果。也就是说在Java中,具体调用某一个方法,不是取决于所声明的类,而是取决于所引用的实例,比如上面例子中,call(Animal1 animal1)其实声明的是Animal类,但是实际上如果传入的是Dog实例,那么最后也就得出Dog类的结果。

而在Kotlin的扩展函数中却是反过来的,扩展函数不支持多态,调用也只取决于对象的声明类型。

同样的,将上面例子中的类用Kotlin写一遍

open class Animal

class Dog : Animal()

//扩展函数
fun Animal.run() ="Animal run"
//扩展函数
fun Dog.run() = "Dog run"

fun person(animal: Animal) {
    println(animal.run())
}

fun main(args: Array<String>) {
    person(Animal())
    person(Dog())
}

image.png

从上图结果可以看出,当传入Animal实例时,运行正常,打印出“Animal run”,但同时传入Dog实例,抛出了ClassCastException异常

Exception in thread "main" java.lang.ClassCastException: com.kotlin.Dog cannot be cast to com.kotlin.Animal

验证可以得知扩展函数与Java不同,并不支持多态。

四、扩展函数的作用域

首先我们得清楚两个名词得概念,1:扩展接收者,2:分发接收者

扩展接收者:扩展函数所要扩展得那个类的实例;

分发接收者:扩展函数定义在哪个类里面,那个类的实例就叫做分发接收者。

举个栗子来说,

class Animal1{
    fun run() {
    }

    fun Dog1.call() {
    }
}

class Dog1{
    fun eat() {
    }
}

在Animal1类中定义了一个Dog1的扩展函数call(),那么Animal类即分发接收者,Dog1为扩展接收者;

作用域的第一条就是扩展函数都可以调用分发接收者和扩展接收者中的函数,即

fun Dog1.call() {
        run()//分发接收者Animal1中的函数
        eat()//扩展接收者Dog1中的函数
    }

那么问题来了,当分发接收者和扩展接收者中都有一个相同名称的方法,那么扩展函数究竟调用的是哪个类的函数??

又双叒叕举个栗子。

fun main(args: Array<String>) {
    Animal2().test()
}

class Animal2 {
    fun run() = "is Animal2"

    fun Dog1.call() {
        println(run())
    }

    fun test() {
        Dog1().call()
    }
}

class Dog1 {
    fun run() = "is Dog1"
}

Animal2类和Dog1类中都存在一个run(),根据作用域第一条的说法,扩展函数可以同时调用分发接收者和扩展接收者中的函数,即可以调用Animal2类和Dog1类中的函数,那么就在扩展函数中调用一下这个相同名称的run()方法。

两个类中都有这个run,那么扩展函数到底调用的是哪一个类的函数呢??咱们验证一下。

image.png

可以看到控制台上打印的字符串是“is Dogs1”,也就是说,当扩展函数调用分发接收者和扩展接收者中相同名称的方法,实际引用的是扩展接收者的实例。这也就引出作用域的第二条:

当扩展接收者和分发接收者中存在相同名称的方法,扩展函数都调用之,扩展接收者的优先级最高。