方法调用,分为两种。解析调用和分派调用。
解析调用
方法调用的任务不是方法执行,而仅仅是确定,要调用的方法的版本。
第一种是在类加载时候的解析阶段就能够确定方法调用的版本的,这种情况被称作是“解析”
第二种是在运行时候才能确定调用方法的版本,这成为“动态分派”。
先说说第一种,“解析”
能够在类加载的解析阶段就确定调用版本的,有两种函数,第一是private函数,第二是静态方法。这两种函数都不可能被继承或者是重写,所以适合在加载阶段进行解析。解析过程是一个静态的过程,在类加载的时候直接把符号引用转换为直接引用。
分派调用
分派调用可能是静态的,也可能是动态的。
静态分派
“重载”函数,它的分派调用就是静态的。【个人感觉,静态分派是针对重载来说的。比如说同一个类中有多个同名函数,但是参数不同。比如,一个函数的参数是父类,另外有两个其他函数的参数是子类。此时,如果调用函数,那么传进来的参数的静态类型决定了要调用的是哪个函数】
父类 a=new 子类
上面一行代码中,a是变量的静态类型,new出来的对象是对象的实际类型,在重载函数的调用过程中,是根据变量的静态类型来确定调用版本的。所以这里如果用aVariable.test(a)来调用函数的话,那调用的会是以父类为参数test函数。
动态分派
【动态分派技术个人认为是针对“覆盖”父类的方法而言的。使用父类引用引用了子类的对象。那么在调用函数的时候,如果子类覆盖了父类的方法,那么调用的就是子类的方法。否则调用父类的方法】
动态分派是面向对象语言的精髓。在运行时动态确定调用方法的版本。动态分派涉及到一个“多态查找”的过程。
父类 a=new 子类
在调用a.test()函数的时候,是根据“实际类型”来确定调用的函数的,那么在调用test函数的时候,a的实际类型是子类,那么就会去调用子类中“重写”的test函数,如果发现子类中没有重写test函数,那么就向上查找父类,调用父类中的test函数。
虚拟机在执行函数调用的时候,如果每次都进行这样的“多态查找”,那么性能就可想而知了。由于动态分派是非常频繁的动作,所以虚拟机在实现的时候,常用的优化方法是,在类的方法区中建立一个“虚方法表”,虚方法表中存放了各个函数的实际入口地址。如果某一个方法在子类中进行了重写,那么子类的这个地址就是自己的方法的地址,如果子类没有重写,那么子类方法的地址就也指向父类方法的地址【即父类和子类共用一个函数】。