静态与动态绑定¶
绑定¶
一个方法的调用与方法所在的类(方法主体)的关联
静态绑定¶
- 在程序执行前方法已经被绑定(也就是说在编译过程中就已经知道这个方法到底是哪个类中的方法),此时由编译器或其它连接程序实现。
- final,static,private和构造方法是静态绑定
调用的目标方法的具体内存地址,在编译阶段就已经在该类的常量池中记录。
动态绑定¶
在运行时根据具体对象的类型进行绑定。
例如,父类和子类的转换声明。根据对象(father)的声明类型(Father)还不能够确定调用方法f1的位置,必须根据father在堆中实际创建的对象类型Son来确定f1方法所在的位置。
class Father{
public void f1(){
System.out.println("father-f1()");
}
public void f1(int i){
System.out.println("father-f1() para-int "+i);
}
}
class Son extends Father{
public void f1(){ //覆盖父类的方法
System.out.println("Son-f1()");
}
public void f1(char c){
System.out.println("Son-s1() para-char "+c);
}
}
//调用方法
public class AutoCall{
public static void main(String[] args){
Father father=new Son(); //多态
father.f1(); //打印结果: Son-f1()
}
}
多态调用字节码¶
0 new hr.test.Son [13] //在堆中开辟一个Son对象的内存空间,并将对象引用压入操作数栈
3 dup
4 invokespecial #7 [15] // 调用初始化方法来初始化堆中的Son对象
7 astore_1 //弹出操作数栈的Son对象引用压入局部变量1中
8 aload_1 //取出局部变量1中的对象引用压入操作数栈
9 invokevirtual #15 //调用f1()方法
12 return
多态原理¶
种类¶
- 重载(编译期):类中可以创建多个方法,它们具有相同的名字,但可具有不同的参数列表、返回值类型
- 实现:根据变量的静态类型确定执行哪个方法
- 重写(运行期):子类继承父类中的方法,重写的参数列表和返回类型均不可修改
内存区域¶
堆存的是就是我们建立的一个个实例对象,而方法区存的就是类的类型信息。
在方法区中,这个class的类型信息只有唯一的实例(所以方法区是各个线程共享的内存区域),而在堆中可以有多个该class对象。也就是说方法区的类型信息就是像一个模板,那些class对象就好比通过这些模板创建的一个个实例。
例子¶
Father sonA=new SonA();
Father sonB=new SonB();
- Father sonA是一个引用类型,存在了java栈中的本地方法表中了。
- new SonA其实创建了一个实例对象,存储在了java堆中。
- SonA的类型数据存在了方法区中
流程¶
- 虚拟机通过reference(Father的引用)查询java栈中的本地变量表,得到堆中的对象类型数据的指针,
- 通过到对象的指针找到方法区中的对象类型数据
- 查询方法表定位到实际类(SonA类)的方法运行。
方法表¶
方法表存在于方法区中,保存的是实例方法的引用,而且是直接引用。java虚拟机在执行程序的时候通过这个方法表确定运行哪一个多态方法。
当Son类的方法表会有一个指向Father类该方法的指针,同时也有一个指向自己该方法的指针,这时候,新的数据会覆盖原有的数据,也就是说原来指向Father.method的那个引用会被替换成指向Son.method的引用(占据原来表中的位置)
类调用是根据多态方法在方法表中的位移量,而接口调用是根据搜索整个方法表。