kotlin的拓展函数和原理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了kotlin的拓展函数和原理相关的知识,希望对你有一定的参考价值。

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() = "狗在叫"

运行结果如下:

(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()方法,可以得出以下结果: 可以看出,在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())

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

fun main() 
    Parent().foo()


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


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

运行结果如下:

问题总结

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

以上是关于kotlin的拓展函数和原理的主要内容,如果未能解决你的问题,请参考以下文章

kotlin—lazy及其原理

kotlin中委托的概念和原理

Kotlin 知识梳理 | 基础概念一览

kotlin协程硬核解读(3. suspend挂起函数&挂起和恢复的实现原理)

kotlin协程硬核解读(3. suspend挂起函数&挂起和恢复的实现原理)

kotlin协程硬核解读(3. suspend挂起函数&挂起和恢复的实现原理)