鉴于上一篇我们写了比较多理论的东西,显得比较枯燥,这次我们从代码实例出发,分析一下:

输出结果为:

第一个结果是重载,很明显执行的方法是

第二个结果是多态,很明显执行的方法是

考虑一下,为什么这两种结果会有区别,

应该有一部分同学会认为会输出apple name is apple
我们从字节码角度来解释一下:

这个是main方法反编译的code部分
我们看18行和22行

对应就是源代码的

我们发现,多态的方法在反编译的时候,也是指向的Fruit的test方法。那为什么结果却是执行子类的方法呢。
我是这么理解的:执行方法分三个部分,即:实际调用的对象.方法(参数类型 参数)
在运行阶段,invokevirtual先找实际调用的对象内有没有符合方法名和参数都一致的方法,如果找到了就执行,如果找不到的话,一层一层的往上找。如果找遍了父类还是没有合适的方法,那就报方法没有找到错误。
那么代码在执行testReload.test(f)时,实际的调用对象是testReload,而方法名和参数类型都是静态匹配的,分别是test和Fruit。所以执行的是:

而在执行f.test()时,实际的对象时Apple的对象,所以时在Apple的实例内找名称为test,传参为空的方法。然后就找到了

实际上,编译是静态的行为,所以编译阶段并不知道你的实际指向的对象是什么,只能确定这个引用的类型是什么。所以在前端编译阶段,f的类型就是Fruit。
咱们在这个基础上加一段代码:

我们加了一句f.test1(),很明显直接编译都不通过,

具体错误是这样提示的,意思就是Fruit类里没有test1方法。根本上编译还是认为f的类型就是Fruit,并不会应为你赋予的具体对象的类型而改变。我们再改造一下代码:

改成这样之后会输出:

这个改变也再次证明了,f的类型是固定的,但是它指向的对象是可以变化的。
所以编译阶段都是静态来确定方法的,只有在执行阶段,才会动态的确定具体的对象类型。
我们把多态方式调用具体对象代码的方式叫做动态分派,把重载叫做静态分派,也有人把重载叫做伪动态分派。
多态、重载、反射、动态代理、持有引用、这些组合起来,可以让你的代码动起来,很多框架和设计模式都是利用这些特性来实现的。之后在设计模式和源码阅读的文章中,我们再具体找例子讲解。
文件预告:后面JVM会有三篇左右,分别是运行时数据区各部分的结构及作用、垃圾回收算法及监控工具、jvm参数及垃圾回收器
网友评论