kotlin的拓展函数和原理

时间:2023-02-03 16:01:30

kotlin的拓展函数和原理

问题背景

kotlin的使用过程中有个拓展函数的概念,这个概念在java中是没有的,那么问题来了,kotlin中拓展函数是什么呢? 拓展函数的概念:不改变原有类的情况下,增加新的方法,扩展新的功能。下面一起看下具体的使用和原理分析。

问题分析

(1)kotlin中使用拓展函数

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

class Dog{
    fun run() = "狗在跑"
    fun eat() = "狗在吃东西"
}

狗狗本身就有跑和吃两个技能,而现在需要增加叫的技能,那就用扩展函数来进行扩展。在需要被扩展的类的后面,添加一个方法即可,如下: fun DogKt.order() = "扩展功能-》狗听从指令" 创建好拓展函数后,调用如下所示:

fun main() {
    val dog = Dog()
    println(dog.run())
    println(dog.eat())
    // 调用dog的拓展函数
    println(dog.cry())
}

class Dog{
    fun run() = "狗在跑"
    fun eat() = "狗在吃东西"
}

fun Dog.cry() = "狗在叫"

运行结果如下: kotlin的拓展函数和原理

(2)拓展函数原理分析

将上面的kotlin代码反编译成java代码如下(具体反编译方法可参考 https://blog.51cto.com/baorant24/6034450 (2)中介绍):

// TestKt.java
...
public final class TestKt {
   public static final void main() {
      Dog dog = new Dog();
      String var1 = dog.run();
      System.out.println(var1);
      var1 = dog.eat();
      System.out.println(var1);
      var1 = cry(dog);
      System.out.println(var1);
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }

    // 拓展函数对应的代码
   @NotNull
   public static final String cry(@NotNull Dog $this$cry) {
      Intrinsics.checkNotNullParameter($this$cry, "$this$cry");
      return "狗在叫";
   }
}
// Dog.java
...
public final class Dog {
   @NotNull
   public final String run() {
      return "狗在跑";
   }

   @NotNull
   public final String eat() {
      return "狗在吃东西";
   }
}

拓展函数的原理:由反编译的java代码很容易看出,kotlin的拓展函数并没有改变对应类本身的结构,也就是说拓展的类本身并没有真的增加方法。而是增加了一个方法,将拓展的类对象作为方法的第一个参数传入,然后进行对应的调用。

(3)拓展函数的限制分析

了解了拓展函数的原理之后,我们来分析下类拓展函数的部分限制。 不能访问私有成员 由于编译成java之后,生成的拓展方法实际是靠第一个参数出入对象引用,然后使用这个对象引用去调用对象的方法。因此我们并没有权限在拓展函数里面调用私有方法:

class TestExt {
    fun publicFun() {}
    private fun privateFun() {}
}

fun TestExt.extFun() {
    publicFun() // 正确,可以调用公有方法

    privateFun() // 错误,不能调用私有方法
}

扩展函数不支持多态 我们可以先看看Java中的多态:这里有一个父类Aninal,里面存在一个run()方法,一个子类Dog继承Animal,类里面同样有一个run()方法,另外有一个调用类Person,存在一个call(Animal animal)。

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

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

//第三个调用类
public class Person {
    public String call(Animal animal1) {
        return animal1.run();
    }

    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person.call(new Animal()));
        System.out.println(person.call(new Dog()));
    }
}

这个时候通过Person类分别来传入Animal和Dog的实例,都调用run()方法,可以得出以下结果: kotlin的拓展函数和原理 可以看出,在Java中,具体调用某一个方法,不是取决于所声明的类,而是取决于所引用的实例对象,比如上面例子中,call()方法其实声明的是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() {
    person(Animal())
    person(Dog())
}

运行结果如下: kotlin的拓展函数和原理 由运行结果可以看出,方法声明的是父类,调用的拓展函数就是父类的拓展函数,不会调用具体子类对象的拓展函数。 成员函数优先级高,拓展函数不能实现重写 当拓展函数与类本身或者父类的成员函数相同,在实际调用的时候会优先调用成员函数,并不会出现类似重写的效果. 例如我们为一个类编写了一个与成员函数相同的拓展函数,实际优先调用类成员函数,代码如下:

fun main() {
    Parent().foo()
}

open class Parent {
    fun foo() {
        println("foo")
    }
}

fun Parent.foo() {
    println("parent")
}

运行结果如下: kotlin的拓展函数和原理

问题总结

本文主要介绍了kotlin中类拓展函数的概念和原理,同时对拓展函数的部分限制做了说明,有兴趣的同学可以进一步深入研究。