静态解析与动态链接
要了解重载与重写,首先需要了解java的动态链接与静态解析。
静态解析:有些符号引用在类加载阶段或者第一次使用时,就会转换为直接引用(内存中的地址),这种转换叫做静态解析。
动态链接:还有一些符号引用则是在每一次运行期间转换为直接引用,这种转换叫做动态链接(java多态的体现)。
在java虚拟机中一共有5个方法调用字节码指令:
invokestatic:调用静态方法。
invokespecial:调用实例构造器方法、私有方法、父类方法
invokevirtual:调用所有虚方法
invokeinterface:调用接口方法,会在运行时确定一个接口实现类。
invokedynamic:动态调用。
其中符合静态解析的是:invokestatic和invokespecial。即:静态方法、私有方法(无法被复写)、实例构造器、父类方法。这4中调用在类加载的时候会把符号引用解析为该方法的直接引用。
方法重载:
1、方法重载是指在一个类中,方法的名称相同,方法的输入参数个数不同,或者参数的类型不同。
方法重载符合静态解析,应为在类加载阶段,方法中的参数类型已经确定,要调用哪个方法也已经确定,可以直接将符号引用替换为直接引用。
可以看如下一段代码的输出结果,加深对重载以及静态解析的理解
public class MyClassTest4 {
private void setParent(GroundParent p) {
System.out.println("GroundParent");
}
private void setParent(Parent p) {
System.out.println("Parent");
}
private void setParent(Son p) {
System.out.println("Son");
}
public static void main(String[] args) {
GroundParent p1 = new Parent(); //声明是静态类型GroundParent,new是实际类型
GroundParent p2 = new Son();
MyClassTest4 mc = new MyClassTest4();
mc.setParent(p1);
mc.setParent(p2);
}
}
class GroundParent{}
class Parent extends GroundParent{}
class Son extends Parent{}
结果输入是:GroundParent。
分析:对于变量来说,变量的声明式静态类型,比如上面的GroundParent;new是实际类型,比如Parent和Son。对于重载方法来说,看的是变量的静态类型,而p1和p2的静态类型都是GroundParent。所以在类加载的初始化阶将方法多大的符号调用替换为参数为GroundParent的方法直接引用。
方法重写(复写):
子类覆盖父类的方法。被重写的方法被调用,无法确认调用者是父类本身还是子类,只有等到运行期,对调用者进行类型确认后才能确定调用方法的直接引用。
方法重写是动态分派的体现。
动态分派是指在运行期才能根据实际类型确定方法执行版本的分派过程。
动态分派中将方法调用者称作接收者
看下面一端代码的编译后的二进制字节
public class MyTest4 {
public static void main(String[] args) {
T t = new T(); //声明类型一样,实际类型不一样
T son = new Tson();//声明类型一样,实际类型不一样
t.say();
son.say();
}
}
class T {
public void say() {}
}
class Tson extends T{
public void say() {}
}
查看编译后的二进制字节码:
可以看到invokevirtual指令后面的参数一样。那么JVM如何阿紫运行期区分不同的实际类型那?
invokevirtual指令的运行时解析过程:
1)找到操作数栈顶的第一个元素(接收者)所指向的对象的实际类型,记作C
2)如果在C中(常量池中)找到与常量中的描述符和简单名称都相同的方法,并且访问权限校验通过,则返回这个方法的直接引用,查找结束;如不通过,返回访问权限错误。
3)否则,按照继承关系从下往上对C的各个父类进行第2步的搜索和校验过程
4)如果始终找不到,抛出AbstractMethodError异常。
虚方法表
由于动态分派非常频繁,搜索会很耗时,所以常用的优化手段就是在方法区建立一个虚方法表(vritual method table,也成为了vtable);对应地,在invokeinterface执行时也会用到接口方法表( interface method table,也叫itable),使用虚方法表索引来代替元数据查找以提高效率。
1)虚方法表中存放着各个方法的实际入口地址;
2)如果某个方法在子类中没有被复写,那么子类的虚方法表里面的地址入口与父类相同方法的地址入口一致,都指向父类的实现入口
3)如果自诶中重写了这个方法,子类方法表中的地址会替换为指向子类实现版本的入口地址。
4)为了实现上的方便,须有相同签名的方法,在父类、子类的虚方法表中都应该具有一样的索引序号,这样当类型变换时,仅需要变更查找的方法表,就可以从不同的虚方法表中按索引转换出所需要的入口地址。
方发表一般在类加载的连接阶段进行初始化(连接的解析阶段),准备了类的变量初始值后,虚拟机会把该类的方发表初始化完毕。