JVM引擎执行一个方法时,需要确定一个方法该执行的版本,页就是需要将字节码文件中的符号引用转换成内存中具体方法的地址。
方法调用源代码
是写在java源代码中的方法调用信息,转换成字节码时,需要用一个字符串也就是符号引用来表示,对于方法重载的情况,这个时候需要选择一个方法表示当前代码调用的方法。
符号引用
是用一个字符串表示一个方法。存储在方法区的常量池中。
直接引用
运行阶段才知道的方法在内存中的入口地址。
C++在编译时链接,确定直接引用。
Java是在解析阶段和运行阶段才将符号引用转换成直接引用。
编译阶段
将一个方法的符号引用存储到字节码中,这时候可能需要将重载的多个方法中找到一个确定的。
加载(解析)阶段
将非虚方法的符号引用转换成直接引用。
运行阶段
将虚方法的符号引用转换成直接引用。
非虚方法
在编译阶段就知道用哪个版本的方法
静态方法 invokestatic
私有方法 invokespecial
构造方法 invokespecial
父类方法 invokespecial
Final方法 invokevirtual
只有一个版本的方法。
有多个版本,但是方法重载,在编译阶段可以确定一个的方法。
虚方法
在运行时才确定执行哪个版本的方法,这里和多态对应。
invokevirtual
父类,子类有多个版本的方法。
父类,子类有多个版本,同时又存在重载的方法。
多态
方法重载
同一个类内,方法名,参数列表不同的多个版本的方法。
方法重写
父类和子类中,子类重写了父类的同名方法。当用父类声明一个变量时,调用该方法名称,不确定使用的是哪个版本的方法。
静态分派
依赖方法所属类确定查找范围,依赖参数类型和参数值的静态类型来确定调用版本的办法。
Class Test{
sayHallo(Human person);
sayHello(Man person);
}
Human person = new Man();
sayHello(person);
调用的是Human那个方法。
静态分派对应方法重载。
静态分派是在编译阶段完成的,帮忙从代码中的方法调用确定了一个调用方法的符号引用。
因为依赖多个因素,所以也称为静态多元素分派
动态分派
依赖方法调用对象的动态类型来确定调用版本的办法。
Class Human{sayHello(){};}
Class Man extends Human{sayHello();}
Human person = new Man();
Person.sayHello();
调用的是Man中的方法。
动态分派对应方法重写。
动态分派是在运行期完成的。
因为依赖单个因素,所以也称为动态单元素分派。
这里的分派只是一个办法,并不是说只能用于虚方法,用于运行期确定方法版本。
动态分派可能是。
静态分派也可用于非虚方法。
一个方法办法的确定可能既存在重载也存在重写,静态分派,动态分派都在使用。
既需要静态分派也需要动态分派的情况,先是编译阶段的静态分派,然后再将符合引用到运行期进行动态分派。也就是先处理重载,后处理重写。
class Father {
public void sayHello(Human person);
public void sayHello(Man person);
}
class Son extends Father{
public void sayHello(Human person);
public void sayHello(Man person);
}
Father object = new Son();
Human person = new Man();
object.sayHello(person);
第一步,编译阶段,进行静态分派,在Father的范围内,找参数类型和参数值的静态类型一致的方法,选择了Father的sayHello(Human),将该方法的符号引用存储到了字节码中。
第二步,运行阶段,进行动态分派,根据调研对象的实际类型来找方法,选择了Son的sayHello(Human),确定了该方法的直接引用。
静态类型
动态类型
Human man = new Man();