静态与动态绑定

绑定

一个方法的调用与方法所在的类(方法主体)的关联

静态绑定

  • 在程序执行前方法已经被绑定(也就是说在编译过程中就已经知道这个方法到底是哪个类中的方法),此时由编译器或其它连接程序实现。
  • 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的类型数据存在了方法区中

流程

  1. 虚拟机通过reference(Father的引用)查询java栈中的本地变量表,得到堆中的对象类型数据的指针,
  2. 通过到对象的指针找到方法区中的对象类型数据
  3. 查询方法表定位到实际类(SonA类)的方法运行。

方法表

方法表存在于方法区中,保存的是实例方法的引用,而且是直接引用。java虚拟机在执行程序的时候通过这个方法表确定运行哪一个多态方法。

当Son类的方法表会有一个指向Father类该方法的指针,同时也有一个指向自己该方法的指针,这时候,新的数据会覆盖原有的数据,也就是说原来指向Father.method的那个引用会被替换成指向Son.method的引用(占据原来表中的位置)

类调用是根据多态方法在方法表中的位移量,而接口调用是根据搜索整个方法表。