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中类拓展函数的概念和原理,同时对拓展函数的部分限制做了说明,有兴趣的同学可以进一步深入研究。