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